-
Notifications
You must be signed in to change notification settings - Fork 148
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
State machine system #389
State machine system #389
Conversation
This is all good and well; now go and write a Behaviour tree system please ;) I'm kidding (partially, BTs would be great!). Great job @BaerMitUmlaut; congratulations! |
@BaerMitUmlaut, I think it would be great if the system auto created a namespace for each state machine (with |
Each state machine is already a CBA namespace 👍 |
Hold on, I was editing the comment; I meant for every ITEM. EDIT: Maybe it's overkill; e.g. I thought group didn't support setVariable. As for mission events, I assume those will usually will contain one item in the list only, so they can use the state machine namespace anyway. |
Also, so other random thoughts:
|
@@ -0,0 +1,79 @@ | |||
/* ---------------------------------------------------------------------------- | |||
Function: CBA_statemachine_fnc_addState |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wrong function name
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
EVERY
TIME
;_;
|
The following was added:
|
GVAR(stateMachines) pushBack _stateMachine; | ||
|
||
if (isNil QGVAR(pfh)) then { | ||
GVAR(pfh) = [FUNC(clockwork), 0, []] call CBA_fnc_addPerFrameHandler; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
optional parameter to set this value for you fsm?
Let me illustrate what I was thinking about with an example: suppose I'm doing a FSM to control the behaviour of a zombie (yes, it will happen for sure!). I might have a "sleeping" state for zeds that are war away from the player. That state is very low prio, so i don't want its code or transitions to be evaluated the same amount of times per second as those awaken zeds, maybe in "chasing" state. Maybe I want those items ticked a maximum of once every 5 secs, while I want "chasing" zeds to be evaluated as often as possible. That much is clear to me; what I'm not sure about is how you'd implement such a thing, because it'd mean that not all the items on the list are evaluated as often as each other, so it's harder to know when the list should be updated. |
Questions: What if two Will it only evaluate the transitional conditions of the current state or more (like how the vanilla animations system picks transitional animations via connectTo and connectFrom). |
class Initial { | ||
onState = ""; | ||
onStateEntered = ""; | ||
onStateLeaving = ""; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"Entered" vs "Leaving". I think you should probably conyugate those two consistently.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's on purpose, it follows an event naming convention from Microsoft. The point is that it tells you already exactly when this happens:
- entered means you're already within the new state (thus past-tense)
- leaving means you're still in the state, but are about in progress of leaving it (thus gerund)
More info: https://msdn.microsoft.com/de-de/library/h0eyck3s(v=vs.71).aspx
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very Microsoft
onTransition is executed when the transition condition is evaluated to true. FSM is then transicioned to the target state
Only the first is followed AFAIK.
Only of the current state |
So in case: ? |
@commy2: Pretty much like esteldunedain already said:
Right in between, as mentioned in the OP execution order is:
They're being checked in sequence, the transition of first one returning true will get chosen.
Yes. Each state only cares about its own transitions. @esteldunedain:
I know somebody who is already working on that 😄
You could do a time check, and if the item hasn't slept in that state for x seconds, skip to the next item (so no conditions nor the onState function would get executed). Would that be what you're thinking of? |
1b0e1fc
to
f45489d
Compare
I notcied something that was lacking, a |
The order of the function arguments seems fine to me. |
I think this is great, but I think an event driven statemachine would be much more suitable for the arma context. |
I'm not sure what you mean. I think the purpose of this is to replace the existing FSM, which work similarly to this PR. If you mean that transitions should be able to be triggered through events, I think it's possible to achieve with the current implementation. |
I think he means that if an item on the list is found to be null when evaluating a state that it should be skipped. |
The isNull skipper simply skips elements that are @Glowbal: You can easily raise events from within the functions. Making this event based would make it a bit too cluttered in my opinion. |
👍 |
This is probably not the place for it but I'll give it a shot anyway. Isn't the |
The state pattern's intend is to
How many times it runs is not relevant for the state pattern. The only thing that matters is that each state has the same interface (in this case, the
So not after one run, but when something specific occurs that makes it appropriate to switch to a new state. One example (shameless copied from wikipedia, if you are going to look it up) is a turnstile. It has two states; locked and unlocked. Push (input) while it's locked and it will not move. Throw a coin in it when it's locked (the input) and it will go to unlocked. When unlocked, push towards it and it will turn. After the turn, it will transition into the locked state. So some states could depending on input, transition right into a next state and "only run it once", while others may run an undefined amount of time. It all depends on your design and intend. tldr; this implementation is a valid state machine, or at least as close as you are going to get within sqf. |
Thanks, makes sense. I was thinking more along the lines of how the FSMs work in Arma. |
Sorry to bring this up after several years, but there seems to be no information on this topic elsewhere. I wonder - what makes your implementation of FSM so much more performant, as you are saying? |
The gist of it is that in the CBA state machine exactly one entity is handled per frame. So if you want to handle AI behaviour for example, it doesn't matter how many AI you spawned, the state machine will always use up the same amount of performance and you won't get any frame degradation because of it. I've also written a detailed post about it on the BI forum which might help you, too (seems like the forum is kind of broken right now, you should still be able to read it though). |
This PR adds a state machine system, similar to BIs FSM, but much more performant (and not finite) and script/config based. It was originally ported from an AI system I'm working on, but has been pretty much rewritten since - thus I could need a second pair of eyes to look over it.
Theory
A state machine consists of a set of states which are connected by transitions. Each state machine starts at a defined initial state and will transition to another state, if the transition's condition is fulfilled. If it cannot transition, it simply stays at its current state.
Implementation
State machines can either be predefined through config or can be dynamically created through function calls. Upon creation, a list the state machine iterates over is defined and afterwards states and transitions are added which have code attached to them.
The state machines themselves run in a "clockwork": every tick (frame), all state machines get "executed" but only process one element each.
The list can either update itself automatically (just pass code as the list argument), be static or get updated manually. The automatic update happens when the list was fully iterated over, the manual update can be done by calling a function.
Use cases
Although this was made with AI systems in mind, this can be used for anything. You can write whole addons only with this.
The advantage of this whole system is that you can iterate over large lists of things and execute code depending on their status or a certain condition - whilst being extremly performant. This isn't perfect for frame critical tasks, however this isn't an issue most of the time. Even with 60 list elements, all elements will processed within one second (at 60fps).
Examples
I've added a very simplistic example in the files (
example.sqf
andexample.hpp
), but here's some more ideas how you could use this: