Internal development notes, very slightly cleaned up and commented a week later.
TL;DR: Vitals state machine, sleep and hunger, better coordination between systems
This week finally saw the first working implementation of V3 vitals. Vitals are meant to be implemented as any other entity system, and there’s also a base state machine for vitals that require it. Most of the work was centered on this new state machine, starting from the existing job-logistic-machine but ending up in a quite different system, down to the execution level.
- proto vitals fullfilling system a system increases/decreases the vital depending on stats and the current state of the entity sleep-system musts on sleep-component entities regularily decreases them each vital has system, and maybe a SM if required much better if job-like stuff needs to be done can be extended from a base SM uses the busy priority system to override as required higher prio than jobs, for now same prio for all vitals test sm for sleep, use job sm as base 0) busy takeover at the right sleep level 1) find available bed 2) if found, act as job on it 3) award large +delta sleep while pefroming job
The previous update mentioned the basic model for vitals. It was fleshed out during this week, first with the introduction of a preliminary execution system and a test state machine for sleep based in in the job machine.
- "hide while action" flag for beds add accessors to pivot helpers add new "hide" pose with empty sprite, use in sleep sm
Some basic features, like hiding entities, are still surfacing as new requirements. For many of these I prefer to wait until I have a clear usage of the feature, then decide on the design. In this case hiding an entity is just adding a new pose to its PoseComponent that has empty sprites. This works perfectly and elegantly and does not require any specific HideComponent or visibility flags, which are an obvious path when you only have engine code, but would have ended up being less elegant.
- replace RESOURCE_KIND_* with components add Resource::additionalComponents, simple flag-like comp list that get added to any item of a given Resource the various Item helpers must append the new flags fix planet resource node/extractors to use new system - new components: mineral-component organic-component fluid-component liquid-component gas-component metal-component edible-component drinkable-component
The kind flags for resources have been replaced by components meant to be added to any item entity, which will make them much easier to search for. The new hunger vital system, described later, just reports a requirement for item entities that have the
edible-component, for example.
- new vital-logistic sm, stop using job-logistic sm for vital tasks single-run but resumable when needed can return failure to allow skipping to another vital add helpers to encapsulate start/resume optinonal single item logistics but allows matching by aspect optinonal action object matched by aspect vital-sm-step: single-stop helper for owning an entity and trying to run steps of the vital sm on it allows to not have to run sm steps all the time handles sm failures gracefully (removing busy states), like missing beds when trying to sleep - throw away sleep sm based on job sm and base on new vital sm - vital-sm-step is being too aggressive with attempting to grab busy status it's removing lower prio busyC that could stay for example trying to sleep while working and no beds are available rework vital sm to do a single step without ownership, reporting if it succeded or not, under a can-busy? check then vital-sm-step sets the busy status -> note this is going to instantiate and destroy a sm just for the check, will hammer when in needy status alternative could be always instantiate and keep state around ---> it's a mem vs cpu tradeoff the sm state is not that mem heavy... - idea: use more local SM vars: item-ref object-ref weak refs plus mandatory deref on every node transition, so no fear of staleness - rework vital-sm to first look up action reqs (object and item), then locking them with BusyC, then returning also add a much better ownership check node, based on the relevant internal refs, much more robust - explicit (break) on the vital-sm find-reqs state in order to allow for delayed ownership of the entity by vital-sm-step - (sidenote: this style of SM is more robust than the original job-logistic-sm. combined with proc entities this could be the final winner design pattern for the game AI) - hunger vital reqs on anything edible plus a eating table
The big item for this week is the new
vital-logistic-sm, a state machine for fullfilling vitals. It’s meant to be run only when a vital reaches a thresold that must make its owner perform some action. For example, the base system for a sleep slowly decreases the sleep vital over time, and calls
vital-sm-step on every tick in order to give a chance to either do nothing, spawn a new machine, or tick forward an existing machine.
The machine itself differs from the previous job design in that it looks ups in the same step both the interaction object and the required resource, locking them both at the same time. This improves the robustness of the action. A new pattern that surfaced here is using weak references stored in the internal machine state, instead of using fields inside the entities components. This is much cleaner and has no problems with staleness, since weak references must always be checked before usage, and the node inheritance system is perfect for enforcing this (
check-ownerships is the base node for most of the actual working nodes in the machine).
The simple sleep system was ported from a test implementation using the job machine to the new vital machine in very few lines of code:
(define sleep-sm (state-machine-extend vital-logistic-sm (state-machine (state pre-perform-action (to perform-action #t (ecs-pose actor "hide"))) (state perform-action-step (to perform-action #t (vital-delta actor sleep-component 10) (break))) ))) (define-mod-export (sleep-system-post-process s e) (vital-delta e sleep-component -0.5) (vital-sm-step e state-component: sleep-component state-machine: sleep-sm busy-priority: sleep-busy-priority busy-owner-hint: sleep-busy-owner-hint object-aspect: (ecs-aspect must: (list bed-component ReadyComponent_Kind)) item-aspect: #f duration: 60 phase: (s #@phase) need: (< (vital-factor e sleep-component) 0.2)) )
The result of combining the new hunger and sleep vitals with the existing cooking test job is this: