Community
SCI Programming => SCI Syntax Help => Topic started by: robbo007 on June 18, 2025, 06:46:10 AM
-
Hi guys,
I'm trying to troubleshoot this error. Not an object $ffff. I've resolved the heap space issue by DisposeLoad the region_path script that seemed to be lingering in memory but after adding a few more embellishments to the room 7 which calls room 503 I get the error on init. Commenting out the "timer init" in Rm007 resolves the error so it looks like there is something in the TIMERMANGER_SCRIPT that is tipping the error.
Rm007 does use the timer script but I'm disposing that before loading Rm503.
Rm007 calls timer script via Rm306 using a local variable called timer.
(= timer (ScriptID 306 0))
(timer init:)
I Disposeload both Region path and Rm306 before loading Rm503 where I have the error:
(DisposeLoad TIMERMANAGER_SCRIPT) ; Dispose TIMERMANAGER_SCRIPT before initialising the room
(DisposeLoad TIMER_SCRIPT)
(gRoom newRoom: 503)
Rm306 TIMERMANER SCRIPT:
;;; Sierra Script 1.0 - (do not remove this comment)
;;
;; Timer Manager Script - Handles shared timer across rooms
(script# 306)
(include sci.sh)
(use Main)
(use Timer)
(use Obj)
(use Controls)
(public
timerManager 0 ; This makes it available to other scripts
)
(local
gDhcTimer
gTimeLimit = 50 ; 10 minutes (600 seconds)
)
(instance timerManager of Obj
(properties)
(method (init)
; Initialize the global timer if it doesn't exist
(if (not (IsObject gDhcTimer))
(= gDhcTimer (Timer new:))
(gDhcTimer setReal: self gTimeLimit)
)
)
(method (cue) ; Called when timer expires
(Print "Time's up! Ms. Cheatem is back!")
(gRoom newRoom: 100) ; Failure room
; Clean up timer
(if (IsObject gDhcTimer)
(gDhcTimer dispose:)
(= gDhcTimer 0)
)
)
(method (showTimeLeft &tmp secondsLeft)
(= secondsLeft
(if (IsObject gDhcTimer)
(gDhcTimer seconds?)
else
-1
)
)
(if (== secondsLeft -1)
(Print "No active timer.")
else
(FormatPrint "Time left: %d:%02d" ;printf equivelent
(/ secondsLeft 60)
(mod secondsLeft 60)
)
)
)
(method (dispose)
; Clean up when no longer needed
(if (IsObject gDhcTimer)
(gDhcTimer dispose:)
(= gDhcTimer 0)
)
(super dispose:)
)
)
Any ideas what could be crashing it? So I cant look deeper into it.
Thanks,
-
Looking at disposeload (https://scicompanion.com/Documentation/Procedures/DisposeLoad.html?highlight=dispose), the documentation says it takes a resourceType followed by resource numbers. I don't see a resource type in the code you posted. Maybe switch to disposeScript (https://scicompanion.com/Documentation/Kernels/DisposeScript.html) or see how they dispose of timers in the Sierra releases.
-
Thanks for that Doomlazer,
I think it was a combination of both that and also not disposing of other resources that the script used. This seemed to work fine. I've made sure when existing the rooms its completely disposes it.
(method (newRoom newRoomNum)
; Save room state if needed
; (= [gRoomStates 7] 0) ; Uncomment and define state if needed
; If going to room 503 or 006, dispose timer script
(if (or (== newRoomNum 503) (== newRoomNum 006))
; Save timer state
(= [gTimerState 0] gTimerActive)
(if (IsObject (ScriptID 306 0)) ; timerManager
(if (IsObject (ScriptID 306 1)) ; gDhcTimer (local, selector 1)
(= [gTimerState 1] ((ScriptID 306 1) seconds?))
else
(= [gTimerState 1] 0)
)
((ScriptID 306 0) dispose:) ; Dispose timerManager
)
; Dispose script #306
(DisposeLoad 0 306)
)
; Clean up room resources
(gCast eachElementDo: #dispose)
(gFeatures eachElementDo: #dispose)
(gAddToPics eachElementDo: #dispose)
; Transition to new room
(super newRoom: newRoomNum)
)
)
-
I can see another potential problem. You're defining timeManager as an instance and not as a class. An instance an only override existing methods and properties, not define new ones - so it should be a class. I'm not sure why Companion accepts it, but it could cause hangs or odd crashes. Also, from what you showed us first, there isn't an export #1 in script 306. So (ScriptID 306 1) is likely to be a fatal error.
-
What's the difference between disposeLoad and load anyway? Seems redundant
-
Let's get one thing straight: DisposeLoad is actually called LoadMany. Brian named it such because it does two things.
Example uses provided by Pablo Ghenis himself:(LoadMany VIEW 110 111 201) ;to load a series of views
(LoadMany SCRIPT 110 111 201) ;to load a series of scripts
(LoadMany FALSE 110 111 201) ;to use DisposeScript
And that's the key, really: so long as what/rsType is not false, it Loads each of the listed resources. If it is false, it calls DisposeScript[/i] on each listed item, implied to be a script resource.
So the difference is, Load loads a single resource and LoadMany loads many. This also makes for smaller and more readable code -- (Load VIEW 110) (Load VIEW 111) (Load VIEW 201) would compile to a much bigger block of PMachine bytes than (LoadMany VIEW 110 111 201).
-
OK, I thought it might be LoadMany; which is certainly a better name, IMO. Thank you for clarifying and always good to remember that the SCICompanion documentation might not map 1:1 in all situations.
I don't work with the SC template games much, so the idiosyncrasies throw me off a bit. I think EO's templates correct some of the issues.
I like that you have to specify a resource type; a convenience function with limited convenience if you need multiple types. IDK, maybe just support &rst in Load?
-
IDK, maybe just support &rst in Load?
Load is a kernel function. The only way you're gonna see that support more than one resource type/num pair is if you add it to the interpreter itself.
Even then you'd need a way to specify which arguments are resource types and which are resource numbers -- VIEW or rsView or whatever is just the number 128, so if you do (Load VIEW 110 120 130 SOUND 150) how will it know you mean to load three views (110 120 130) and one sound (150), instead of five views (110 120 130 132 150) and no sounds? And that is why LoadMany takes only one type per call.
Worst thing is, I absolutely could make Load the kernel function work exactly like LoadMany the script procedure. Skips the whole "find and load the LoadMany script, run the function, including iteration, and then dispose of it like it says to" hassle.
-
Load is a kernel function. The only way you're gonna see that support more than one resource type/num pair is if you add it to the interpreter itself.
Some SCI interpreters actually have this. Including the one you've improved.
EDIT: Well, one type and several resource numbers.
-
hold on
*checks source code*
Oh yeah, so it does! (https://github.com/Kawa-oneechan/SCI11-Plus/blob/e157084a227db86b92d71afccf41e7ca54abadce/BASE/KERNEL.C#L140) No disposing though, that part of LoadMany isn't there.
-
On the whole, the Load/UnLoad/DisposeScript trinity was misunderstood. I've seen them misused many times. For one example, we have this in QfG1 (cemetery):
(DisposeScript riseUpLeft)
(DisposeScript riseUpRight)
(DisposeScript riseDownLeft)
(DisposeScript peekABoo)
All four lines refer to instances of Script, not script numbers. "There are two things we at Sierra call scripts" explained the guy in that interview. Yes, and it clearly causes misunderstandings sometimes.
-
At this point I'm thinking, what if
(class Cutscene of Object
(properties
name "Script"
;; rest of the Script class stuff
There, now you can't mistake a Script-as-in-cutscene for a script-as-in-module, and people looking at the result can't tell you did it.
-
Another common mistake I see is doing (Load rsSCRIPT n). This will force some of the disk I/O to take place during room init as intended, but it won't load the corresponding heap resource (in SCI1.1 and higher) and won't instantiate (as it's called in ScummVM) the desired script, or any dependent scripts. That will have to be done later.
-
Is there a correct way to load it?
-
(ScriptID n). Or just name a class in the script.
-
So this is my Timer Manager script in the end. It seemed to cause all types of grief when I tried to have the time state move between rooms and continue where the last room took off. So have it restarting when moving between rooms giving a single time limit per room.
(script# 306)
(include sci.sh)
(include game.sh)
(use Main)
(use Timer)
(use Obj)
(use Controls)
(public
timerManager 0
)
(local
gTimeLimit = 300 ; 300 seconds / 5 mins
gTimerActive
gDeathScriptPending
gQueuedScriptRan
)
(instance timerManager of Obj
(properties)
(method (init)
; Only create new timer if none exists or current one expired
(if (or (not (IsObject gDhcTimer)) (<= (gDhcTimer seconds?) 0))
(self dispose:) ; Clean up any existing timer first
(= gDhcTimer (Timer new:))
(gDhcTimer setReal: self gTimeLimit)
else
; Just refresh the existing timer
(gDhcTimer setReal: self (gDhcTimer seconds?))
)
(= gTimerActive TRUE)
)
(method (cue)
(= gTimerActive FALSE)
; Load room scripts only when needed
(cond
((== gRoomNumber 7)
(if (not (ScriptID 7)) (ScriptID 7)) ; Ensure script is loaded
(if (IsObject (ScriptID 7 1))
(gRoom setScript: (ScriptID 7 1))
else
(self defaultExpiredHandler:)
)
)
((== gRoomNumber 8)
(if (not (ScriptID 8)) (ScriptID 8)) ; Ensure script is loaded
(if (IsObject (ScriptID 8 1))
(gRoom setScript: (ScriptID 8 1))
else
(self defaultExpiredHandler:)
)
)
(else
(self defaultExpiredHandler:)
)
)
)
(method (doit)
; Optimized script queue handling
(if (and (IsObject gDeathScriptPending) (not gQueuedScriptRan))
(gRoom setScript: gDeathScriptPending)
(= gQueuedScriptRan TRUE)
(= gDeathScriptPending 0)
)
(super doit:)
)
(method (defaultExpiredHandler)
(Print "Timer expired in unexpected room!")
)
(method (showTimeLeft &tmp secondsLeft)
(if (not gTimerActive)
(Print "No active timer.")
(return)
)
(if (IsObject gDhcTimer)
(= secondsLeft (gDhcTimer seconds?))
(FormatPrint "Time left: %d:%02d"
(/ secondsLeft 60)
(mod secondsLeft 60)
)
else
(Print "Timer error!")
)
)
(method (dispose)
; More thorough cleanup
(if (IsObject gDhcTimer)
(gDhcTimer client: 0)
(gDhcTimer dispose:)
(= gDhcTimer 0)
)
(= gTimerActive FALSE)
(= gDeathScriptPending 0)
(super dispose:)
)
)
-
Scripts that need to persist between rooms are typically regions so that they don't get unloaded.
-
This is an antipattern:
(if (not (ScriptID 8)) (ScriptID 8)) ; Ensure script is loaded
(if (IsObject (ScriptID 8 1))
In the first line, the if-expression will never be true, because ScriptID always loads the script.
In the second, it will never be false, for the same reason.
Unless you miscompile that script somehow. Sierra just did:
(ScriptID 8)
-
This is an antipattern:
(if (not (ScriptID 8)) (ScriptID 8)) ; Ensure script is loaded
(if (IsObject (ScriptID 8 1))
In the first line, the if-expression will never be true, because ScriptID always loads the script.
In the second, it will never be false, for the same reason.
Unless you miscompile that script somehow. Sierra just did:
(ScriptID 8)
ok thanks. I've updated it now :)