Questions about the SimAntics architecture

Discussion in 'Programming' started by KatsuKitty, Apr 11, 2016.

  1. KatsuKitty

    KatsuKitty New Member

    I'm actually designing a game very similar to The Sims, in that it is structured around a series of script-powered "objects" that sit on a "lot" that "players" can interact with. As it entails, I'm aiming to re-implement many of what SimAntics did for my own game, but there are still a few things that puzzle me about how The Sims was actually designed. Since you guys did a very good job creating what is effectively an open-source version of the SimAntics engine, I figure you might know the technical details of how Maxis designed their engine way better than I do.

    My main question concerns just how and if object execution can be preempted by the game engine. From what I understand, objects are cooperatively multitasked, meaning they need to "cooperate" by relinquishing control back to the VM. Reading what little exists on the way the SimAntics engine works, it appears that a lot will call the main() function on each object in a round-robin fashion, and each object blocks the VM until it releases control via the Sleep primitive. The main() function will not be called again until the Sleep primitive completes its number of ticks. That is to say, the execution of an object can not be preempted, and if it did not return control then the game would freeze. To use an example, a purchased piece of artwork would have a main() that does little more than deprecate its resale value, then Sleep; no other object or interaction is running while the artwork deprecates, and the game world stops for that small time the Expression primitive is run on its value. When you're in main(), nothing else happens. At all.

    However, BHAV interactions (e.g. selecting View from the pie menu on the artwork) is where it appears to get fuzzier. Looking at some tutorials for adding your own BHAV interaction, there are primitives such as bringing the Sim in front of the object to view it. I was under the initial impression that BHAVs started from an interaction ran similarly, as their own "thread" that would need to relinquish control in order to allow other game objects to continue executing. However, in interactions, primitives are set up in such a way that it looks like the game would have to preempt this seemingly cooperative system.

    Here is an object that a Sim may view, and then boost their Hygiene:

    To me, it appears as if the game can preempt the execution of an object after all. The first primitive brings the Sim in front of the painting, from wherever they are standing. The second primitive, increasing the hygiene, cannot be executed until the game finds a path, directs your Sim along the path, and brings it in front of the painting. I know the game certainly doesn't stop everything while the object is executing this, so it implies that the game is indeed able to preempt object execution while it is waiting for the Sim to approach the object. Unlike main(), the game does continue as you're in the process of executing this BHAV.

    So, what is the actual way objects work? Is only the main() function cooperative? Are certain primitives able to preempt object execution "asynchronously", while others run "synchronously" into the next step? The only thing I can think of is that primitives other than Sleep allow relinquishing control to the VM (e.g. "Go To Relative Position" also allows control to be released much like Sleep, and Sleep's "True" evaluation at the end of main() is actually back to the beginning of main()).
    Last edited: Apr 11, 2016
  2. RHY3756547

    RHY3756547 FreeSO Developer Staff Member Moderator

    BHAVs in The Sims can't normally be pre-empted (we do this in extreme cases to crash objects stuck in infinite loops). In the original game, infinite loops actually freeze the game if they're in check trees, and I think object threads do the same thing.

    Primitives in SimAntics actually have a 3rd return value, which we've dubbed "CONTINUE ON NEXT TICK". This keeps the instruction pointer in the same place, but returns control from the thread until the next tick is being executed. For example, Animate Sim primitives will continue on themselves until the animation has ended (TRUE branch) or an event has occured (FALSE branch). Sleep primitives continue on themselves until the target parameter is 0 before decrement.

    Our implementation of the routing primitives is a little more complicated. It pushes a special stack frame called a "routing frame", which uses the same {TRUE, FALSE, CONTINUE} return system to drive how it affects SimAntics, but internally routes and animates the sim frame by frame using a ton of extra state. This stack frame can push other ones to direct sims to run chair sit functions, stand functions, room portal functions, and so on... Not sure how the original did this, but it is probably rather close to our solution. The result is that the routing primitive "manages itself" for a number of frames to attempt to route the Sim to the destination, then just returns a value in place of the primitive a few ticks later when a result has been reached.

    Interactions are run on the Sim thread, so that they don't interrupt that object's main loop (and since the sim is really the one doing something with it). There's a lot more on this concerning interaction queues - but we actually don't have this perfect yet.
    Last edited: Apr 12, 2016
    zero35 likes this.

Share This Page