I’m trying to implement Inform-esque daemon support in a realtime environment.
As far as I understand it, the Inform runtime executes the daemon method for every object that implements it once per turn.
What are your opinions on how this should be emulated in a realtime situation that essentially makes turn-based play impossible?
My current naive approach is to simply scan for all object instances implementing the daemon method at boot, and register them as listeners to a timer event that is invoked once every N milliseconds or so where N is somewhere between 200 and 1000.
However, I am thinking that some sort of persistent scheduling should be implemented instead at some default rate that would be definable per instance. What sort of convention should be used for the definition of such scheduling that would be sufficiently Inform-esque, I am not sure.
Just store the scheduled times the events should fire in game-time. You probably don’t need sub-second resolution for your scheduler so just having a looping timer at 1000ms is probably sufficient. Each time the timer is fired, simply iterate all the scheduled events and fire the ones that have a schedule time that is less-than the current game time. If you keep the list of scheduled events sorted, say precisely inserting newly scheduled events, then you don’t need to iterate through all the events each cycle - you can stop as soon as you hit one that isn’t ready to fire.
Upon game load, scan all objects for the daemon method.
Register all daemon implementing objects in a list.
Create a thread that loops through all daemons in the list and invokes the daemon method once every second.
A convention is left to the designer to diverge from at his or her peril, to make each daemon method be as short-running as possible or to instantiate a new event or thread within which to execute longer running code.
The intention of 4 is that the execution of all registered daemons should only take a few nanoseconds or milliseconds per iteration.
Now. Timers are another story. So are real-time specifically scheduled events such as this pseudo Inform 6 code example:
Object sun "sun"
with sunrise [; "The sun rises." ]
sunset [; "The sun sets." ]
sun.schedule('sunrise', "6:00 am", "12:00 pm", "6:00 pm", "12:00 am")
sun.schedule('sunset', "9:00 am", "3:00 pm", "9:00 pm", "3:00 am")
Or something like
schedule the sun to print "The sun sets." at 6:00 am
Very few objects would exhibit behavior of the chronologically specific sort as the sun setting, but I’m thinking that such schedules would be persisted somehow and marked as ran after its execution has completed. This way, if the program has been shut down, and then restarted later, all persisted schedules scheduled in the past that have not been executed can be immediately invoked (ideally with actual output muted) – simply to ensure that any possible state changes have an opportunity to be at least somewhat resolved.
Looks plausible. When you do the “just started/loaded” dance, I’d say call the daemons with a special flag to indicate that time has passed (and how much). Leave it up to the daemon whether to mute output, shorten the message, or do nothing.
It would be nice to let daemons specify a period (once every 10 seconds, once every 10 minutes, etc) rather than all putting them on a once-per-second clock. This also lets the engine do some optimization.
IF interpreters already assume this is true of all game code. Definitely stick with this policy.
Does Inform currently support a time_passed parameter for daemon routines implemented by objects? Else, how could a daemon routine definition specify this interval? Or would it just be a sort of check on a local attribute or field per object to determine deltas since previous executions?
Ah, I see. So, presently, I do have a per-object property called ‘frequency’ which is settable in-game and in-code. However, having a NPC/mob simply execute some specific thing once every N seconds is probably unrealistic, so I’ve implemented a little helper method which executes wrapped event code ‘occasionally’ in a semi-random interval not to exceed the given frequency. This imbues a daemon implementing an ‘autonomous’ method with more ‘realistic’ random behavior, or at least more believably autonomous behavior.
Specifying a frequency on every single daemon within the code, or after instantiation, while avoiding the situation in which the autonomous behavior of every single daemon was executed simultaneously would require that the initial invocations of the daemons be staggered in some way, perhaps by having each one scheduled individually for its respective initial execution time and subsequent interval. This would mean that scanning would be far less simplistic than simply identifying objects implementing the daemon method, but would additionally involve retrieving each daemon object’s frequency property, somehow identifying or calculating or randomly choosing its initial execution time, and then calculating and storing a “next execution time” after every single execution.
So after all that, it almost seems like it would be nearly equivalent to full-blown scheduling, which is something which I think might be only applicable to a few situations like the sunset/sunrise scenario.
At this time, I only have support for three kinds of autonomous functionality:
Short-term reactionary events triggered by modularly-defined react_after_, react_before_, life_(whatever), etc., methods for responding to actions;
the daemon/each_turn support for invoking a hook from which more complex autonomous “decision-making” code specifying longer-term behavior could be executed;
and then regularly scheduled events which are guaranteed to occur regularly despite any other interaction, unless rescheduled or unscheduled entirely.
It’s the third feature for which I am having difficulty coming up with a convention. I’m thinking the whole sun.schedule(‘sunrise’, “6:00 am”, “12:00 pm”, “6:00 pm”, “12:00 am”) thing is the way I’ll go. However, I imagine that this kind of clock-work autonomy would be appropriate for very few routines, from the perspective of environmental suspension-of-disbelief.