DDirectorWikiDirector & Lingo Encyclopedia

Parent Scripts and Objects

Object-oriented Lingo - parent scripts, child objects, ancestor inheritance, actorList, and timeout objects.

The model

Lingo does OOP with parent scripts (classes) instantiated into child objects (instances). There are no classes as a language construct; a parent script is just a script cast member whose handlers become the methods of every child created from it.

Director termMainstream OOP term
Parent scriptClass
Child objectInstance
property variableInstance field
HandlerMethod
ancestorSuperclass (delegation-based)

Creating objects

lingo
-- Parent script cast member named "Car"
property pSpeed

on new me, initialSpeed
  pSpeed = initialSpeed
  return me                -- REQUIRED: new must return me
end

on accelerate me, amount
  pSpeed = pSpeed + amount
  return pSpeed
end
lingo
-- creating and using child objects
car1 = new(script "Car", 20)          -- classic form
car2 = script("Car").new(30)          -- dot form
car1.accelerate(5)                    -- 25
accelerate(car2, 5)                   -- identical call, verbose form
put car1.pSpeed                       -- properties are externally readable
car1.pSpeed = 60                      -- ...and writable

Facts:

  • me is passed automatically as the first argument of every method call on the object.
  • on new receives the arguments given to new() after the script; forgetting return me yields VOID and is a classic bug.
  • rawNew(script "Car") allocates without running on new (used for deserialization patterns); call new() on it later to initialize.
  • Objects die by losing all references (car1 = 0 or VOID). There are no destructors; cleanup conventions use an explicit destroy()/die() handler. An object in the actorList or a timeout target list is still referenced and will not be collected.
  • objectP(x) tests "is an object"; ilk(x) returns #instance for child objects.
  • The count of live instances is visible in authoring via the perFrameHook debugging tricks, but at runtime reference counting is invisible to Lingo.

Inheritance via ancestor

lingo
-- Parent script "Animal"
property pName

on new me, aName
  pName = aName
  return me
end

on speak me
  put pName && "makes a sound"
end

-- Parent script "Dog"
property ancestor

on new me, aName
  ancestor = new(script "Animal", aName)
  return me
end

on speak me
  put me.pName && "barks"       -- pName resolves through the ancestor
end

Semantics:

  • ancestor is an ordinary property holding another object. Lookup of a handler or property that the child's script does not define falls through to the ancestor, recursively (ancestors can have ancestors).
  • It is delegation, not static inheritance: you can swap ancestor at runtime to change behavior ("state pattern for free"), and multiple children can share one ancestor instance (shared mutable state!).
  • Property lookup goes: the object's own script properties, then ancestor chain. The child "sees" ancestor properties as its own (me.pName).
  • callAncestor(#handler, me, args...) invokes the ancestor's version explicitly (the super-call).
  • Behaviors can also have ancestors; the pattern is identical.

Objects that tick: the actorList

lingo
on new me
  add the actorList, me       -- register for per-frame callbacks
  return me
end

on stepFrame me
  pFrameCount = pFrameCount + 1
  updateMySprites me
end
  • Every object in _movie.actorList receives on stepFrame each time the playback head advances and each time updateStage() runs.
  • stepFrame on actorList members is dispatched by the engine directly and is cheaper than broadcasting custom messages.
  • The actorList is not cleared when the movie branches with go to movie; stale objects from the previous movie keep ticking. Defensive movies clear it in prepareMovie. An emulator must preserve the non-clearing default.
  • Removing: deleteOne(the actorList, me). Never mutate the actorList from inside another object's stepFrame without accounting for iteration (native behavior iterates a snapshot-by-index; removing entries mid-iteration skips or double-calls, an edge worth microtesting when fidelity matters).

Timeout objects

The timer facility for time-based callbacks, independent of frame rate:

lingo
-- MX 2004 forms
tm = timeout().new("heartbeat", 2000, #onBeat, me)   -- every 2000 ms
tm = new timeout("heartbeat", 2000, #onBeat, me)

on onBeat me, aTimeoutObject
  sendHeartbeat me
end

Management:

lingo
timeout("heartbeat").period = 5000     -- retune
timeout("heartbeat").forget()          -- cancel
put _movie.timeoutList                 -- all active timeout objects
timeout("heartbeat").time              -- next-fire time
the timeoutHandler of tm               -- #onBeat

Semantics that matter for compatibility:

  • The handler fires repeatedly every period milliseconds until forget().
  • If targetObject is given, the handler is called on that object; otherwise the message goes to movie scripts.
  • Timeout dispatch happens between frame events on the main thread; a blocking repeat loop delays all timeouts (there is no preemption).
  • A timeout object keeps its target alive (reference). forget() is the only clean release.
  • Do not confuse with the ancient the timeoutScript/the timeoutLength global idle-timeout mechanism (Director 4 era), which still exists as primary-event-handler machinery (timeoutKeydown, timeoutMouse, etc. reset rules) and appears in the native symbol registry.

Patterns in shipped code

lingo
-- Singleton manager
global gRoomManager
on startMovie
  if voidP(gRoomManager) then gRoomManager = new(script "RoomManager")
end

-- Factory + registry
on createFurni me, defId
  obj = new(script "Furni", defId)
  setaProp(pById, obj.pId, obj)
  return obj
end

-- Broadcasting to a list of objects
repeat with o in pListeners
  o.notify(#roomEntered, roomId)
end repeat

Habbo-era codebases (FUSE client) are built almost entirely from parent scripts wired through globals and broadcast lists, with behaviors used only as thin sprite-side shims. Recognizing these three patterns covers most of what you will encounter when reading them.