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 term | Mainstream OOP term |
|---|---|
| Parent script | Class |
| Child object | Instance |
property variable | Instance field |
| Handler | Method |
ancestor | Superclass (delegation-based) |
Creating objects
-- 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-- 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 writableFacts:
meis passed automatically as the first argument of every method call on the object.on newreceives the arguments given tonew()after the script; forgettingreturn meyields VOID and is a classic bug.rawNew(script "Car")allocates without runningon new(used for deserialization patterns); callnew()on it later to initialize.- Objects die by losing all references (
car1 = 0or VOID). There are no destructors; cleanup conventions use an explicitdestroy()/die()handler. An object inthe actorListor a timeout target list is still referenced and will not be collected. objectP(x)tests "is an object";ilk(x)returns#instancefor child objects.- The count of live instances is visible in authoring via
the perFrameHookdebugging tricks, but at runtime reference counting is invisible to Lingo.
Inheritance via ancestor
-- 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
endSemantics:
ancestoris 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
ancestorat 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
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.actorListreceiveson stepFrameeach time the playback head advances and each timeupdateStage()runs. stepFrameon 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 inprepareMovie. An emulator must preserve the non-clearing default. - Removing:
deleteOne(the actorList, me). Never mutate the actorList from inside another object'sstepFramewithout 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:
-- 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
endManagement:
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 -- #onBeatSemantics that matter for compatibility:
- The handler fires repeatedly every
periodmilliseconds untilforget(). - If
targetObjectis 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 timeoutLengthglobal 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
-- 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 repeatHabbo-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.