The Spatials V3 devblog 2016-03-07
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: