From 4d2d3aa1bc5b2a65439aef821fa3667025e6e3e8 Mon Sep 17 00:00:00 2001 From: Moritz Schmidt Date: Wed, 29 Jul 2020 11:43:12 +0200 Subject: [PATCH 1/3] state machines: configurable number of ticks per frame --- addons/statemachine/XEH_PREP.hpp | 1 + addons/statemachine/fnc_clockwork.sqf | 86 +-------------- addons/statemachine/fnc_create.sqf | 18 ++-- addons/statemachine/fnc_createFromConfig.sqf | 4 +- addons/statemachine/fnc_tick.sqf | 106 +++++++++++++++++++ 5 files changed, 122 insertions(+), 93 deletions(-) create mode 100644 addons/statemachine/fnc_tick.sqf diff --git a/addons/statemachine/XEH_PREP.hpp b/addons/statemachine/XEH_PREP.hpp index fe775b8b9..80ca721d1 100644 --- a/addons/statemachine/XEH_PREP.hpp +++ b/addons/statemachine/XEH_PREP.hpp @@ -7,6 +7,7 @@ PREP(createFromConfig); PREP(delete); PREP(getCurrentState); PREP(manualTransition); +PREP(tick); PREP(toString); PREP(updateList); diff --git a/addons/statemachine/fnc_clockwork.sqf b/addons/statemachine/fnc_clockwork.sqf index 4aa130f51..50a0867f6 100644 --- a/addons/statemachine/fnc_clockwork.sqf +++ b/addons/statemachine/fnc_clockwork.sqf @@ -17,89 +17,7 @@ Author: SCRIPT(clockwork); { - #ifdef STATEMACHINE_PERFORMANCE_COUNTERS - private _perfStartTime = diag_tickTime; - #endif - private _stateMachine = _x; - private _list = _stateMachine getVariable QGVAR(list); - private _skipNull = _stateMachine getVariable QGVAR(skipNull); - private _id = _stateMachine getVariable QGVAR(ID); - private _tick = _stateMachine getVariable QGVAR(tick); - - // Skip to next non-null element or end of list - if (_skipNull) then { - while {(_tick < count _list) && {isNull (_list select _tick)}} do { - _tick = _tick + 1; - }; + for "_i" from 1 to (_x getVariable [QGVAR(ticksPerFrame), 1]) do { + [_x] call FUNC(tick); }; - - // When the list was iterated through, jump back to start and update it - if (_tick >= count _list) then { - private _updateCode = _stateMachine getVariable QGVAR(updateCode); - _tick = 0; - if !(_updateCode isEqualTo {}) then { - _list = [] call _updateCode; - - // Make sure list contains no null elements in case the code doesn't filter them - // Else they wouldn't be skipped at this point which could cause errors - if (_skipNull) then { - _list = _list select {!isNull _x}; - }; - - _stateMachine setVariable [QGVAR(list), _list]; - }; - }; - - // If the list has no items, we can stop checking this state machine - // No need to set the tick when it will get reset next frame anyways - if !(_list isEqualTo []) then { - _stateMachine setVariable [QGVAR(tick), _tick + 1]; - - private _current = _list select _tick; - private _thisState = _current getVariable (QGVAR(state) + str _id); - - if (isNil "_thisState") then { - // Item is new and gets set to the intial state, onStateEntered - // function of initial state gets executed as well. - _thisState = _stateMachine getVariable QGVAR(initialState); - _current setVariable [QGVAR(state) + str _id, _thisState]; - _current call (_stateMachine getVariable ONSTATEENTERED(_thisState)); - }; - - // onState functions can use: - // _stateMachine - the state machine - // _this - the current list item - // _thisState - the current state - _current call (_stateMachine getVariable ONSTATE(_thisState)); - - private _thisOrigin = _thisState; - { - _x params ["_thisTransition", "_condition", "_thisTarget", "_onTransition"]; - // Transition conditions, onTransition, onStateLeaving and - // onStateEntered functions can use: - // _stateMachine - the state machine - // _this - the current list item - // _thisTransition - the current transition we're in - // _thisOrigin - the state we're coming from - // _thisState - same as _thisOrigin - // _thisTarget - the state we're transitioning to - // Note: onTransition and onStateLeaving functions can change - // the transition target by overwriting the passed - // _thisTarget variable. - // Note: onStateEntered functions of initial states won't have - // some of these variables defined. - if (_current call _condition) exitWith { - _current call (_stateMachine getVariable ONSTATELEAVING(_thisOrigin)); - _current call _onTransition; - _current setVariable [QGVAR(state) + str _id, _thisTarget]; - _current call (_stateMachine getVariable ONSTATEENTERED(_thisTarget)); - }; - } forEach (_stateMachine getVariable TRANSITIONS(_thisState)); - }; - - #ifdef STATEMACHINE_PERFORMANCE_COUNTERS - private _perfRunTime = diag_tickTime - _perfStartTime; - (GVAR(performanceCounters) select _id) pushBack _perfRunTime; - #endif - } forEach GVAR(stateMachines); diff --git a/addons/statemachine/fnc_create.sqf b/addons/statemachine/fnc_create.sqf index 757ca699e..f2d389a50 100644 --- a/addons/statemachine/fnc_create.sqf +++ b/addons/statemachine/fnc_create.sqf @@ -27,7 +27,8 @@ Author: SCRIPT(create); params [ ["_list", [], [[], {}]], - ["_skipNull", false, [true]] + ["_skipNull", false, [true]], + ["_ticksPerFrame", 1, [0]] ]; if (isNil QGVAR(stateMachines)) then { @@ -52,13 +53,14 @@ if (_list isEqualType {}) then { }; private _stateMachine = call CBA_fnc_createNamespace; -_stateMachine setVariable [QGVAR(nextUniqueStateID), 0]; // Unique ID for autogenerated state names -_stateMachine setVariable [QGVAR(tick), 0]; // List index ticker -_stateMachine setVariable [QGVAR(states), []]; // State machine states -_stateMachine setVariable [QGVAR(list), _list]; // List state machine iterates over -_stateMachine setVariable [QGVAR(skipNull), _skipNull]; // Skip items that are null -_stateMachine setVariable [QGVAR(updateCode), _updateCode]; // List update code -_stateMachine setVariable [QGVAR(ID), GVAR(nextUniqueID)]; // Unique state machine ID +_stateMachine setVariable [QGVAR(nextUniqueStateID), 0]; // Unique ID for autogenerated state names +_stateMachine setVariable [QGVAR(tick), 0]; // List index ticker +_stateMachine setVariable [QGVAR(states), []]; // State machine states +_stateMachine setVariable [QGVAR(list), _list]; // List state machine iterates over +_stateMachine setVariable [QGVAR(skipNull), _skipNull]; // Skip items that are null +_stateMachine setVariable [QGVAR(ticksPerFrame), _ticksPerFrame]; // How many ticks to run on each frame +_stateMachine setVariable [QGVAR(updateCode), _updateCode]; // List update code +_stateMachine setVariable [QGVAR(ID), GVAR(nextUniqueID)]; // Unique state machine ID INC(GVAR(nextUniqueID)); if (isNil QGVAR(efID)) then { diff --git a/addons/statemachine/fnc_createFromConfig.sqf b/addons/statemachine/fnc_createFromConfig.sqf index 3607bb0de..a0a64ae2b 100644 --- a/addons/statemachine/fnc_createFromConfig.sqf +++ b/addons/statemachine/fnc_createFromConfig.sqf @@ -28,7 +28,9 @@ if (isNull _config) exitWith {}; private _list = compile getText (_config >> "list"); private _skipNull = (getNumber (_config >> "skipNull")) > 0; -private _stateMachine = [_list, _skipNull] call FUNC(create); +private _ticksPerFrame = getNumber (_config >> "ticksPerFrame"); +_ticksPerFrame = if (_ticksPerFrame == 0) then {1} else {_ticksPerFrame}; +private _stateMachine = [_list, _skipNull, _ticksPerFrame] call FUNC(create); { private _state = configName _x; diff --git a/addons/statemachine/fnc_tick.sqf b/addons/statemachine/fnc_tick.sqf new file mode 100644 index 000000000..29ec22f39 --- /dev/null +++ b/addons/statemachine/fnc_tick.sqf @@ -0,0 +1,106 @@ +#include "script_component.hpp" +/* ---------------------------------------------------------------------------- +Function: CBA_statemachine_fnc_tick + +Description: + Execute a single tick on a given statemachine + +Parameters: + _stateMachine - statemachine created by CBA_statemachine_fnc_create + +Returns: + Nothing + +Author: + BaerMitUmlaut +---------------------------------------------------------------------------- */ +SCRIPT(tick); + +params [ + ["_stateMachine", objNull, [objNull, locationNull]] +]; + +#ifdef STATEMACHINE_PERFORMANCE_COUNTERS +private _perfStartTime = diag_tickTime; +#endif + +private _list = _stateMachine getVariable QGVAR(list); +private _skipNull = _stateMachine getVariable QGVAR(skipNull); +private _id = _stateMachine getVariable QGVAR(ID); +private _tick = _stateMachine getVariable QGVAR(tick); + +// Skip to next non-null element or end of list +if (_skipNull) then { + while {(_tick < count _list) && {isNull (_list select _tick)}} do { + _tick = _tick + 1; + }; +}; + +// When the list was iterated through, jump back to start and update it +if (_tick >= count _list) then { + private _updateCode = _stateMachine getVariable QGVAR(updateCode); + _tick = 0; + if !(_updateCode isEqualTo {}) then { + _list = [] call _updateCode; + + // Make sure list contains no null elements in case the code doesn't filter them + // Else they wouldn't be skipped at this point which could cause errors + if (_skipNull) then { + _list = _list select {!isNull _x}; + }; + + _stateMachine setVariable [QGVAR(list), _list]; + }; +}; + +// If the list has no items, we can stop checking this state machine +// No need to set the tick when it will get reset next frame anyways +if !(_list isEqualTo []) then { + _stateMachine setVariable [QGVAR(tick), _tick + 1]; + + private _current = _list select _tick; + private _thisState = _current getVariable (QGVAR(state) + str _id); + + if (isNil "_thisState") then { + // Item is new and gets set to the intial state, onStateEntered + // function of initial state gets executed as well. + _thisState = _stateMachine getVariable QGVAR(initialState); + _current setVariable [QGVAR(state) + str _id, _thisState]; + _current call (_stateMachine getVariable ONSTATEENTERED(_thisState)); + }; + + // onState functions can use: + // _stateMachine - the state machine + // _this - the current list item + // _thisState - the current state + _current call (_stateMachine getVariable ONSTATE(_thisState)); + + private _thisOrigin = _thisState; + { + _x params ["_thisTransition", "_condition", "_thisTarget", "_onTransition"]; + // Transition conditions, onTransition, onStateLeaving and + // onStateEntered functions can use: + // _stateMachine - the state machine + // _this - the current list item + // _thisTransition - the current transition we're in + // _thisOrigin - the state we're coming from + // _thisState - same as _thisOrigin + // _thisTarget - the state we're transitioning to + // Note: onTransition and onStateLeaving functions can change + // the transition target by overwriting the passed + // _thisTarget variable. + // Note: onStateEntered functions of initial states won't have + // some of these variables defined. + if (_current call _condition) exitWith { + _current call (_stateMachine getVariable ONSTATELEAVING(_thisOrigin)); + _current call _onTransition; + _current setVariable [QGVAR(state) + str _id, _thisTarget]; + _current call (_stateMachine getVariable ONSTATEENTERED(_thisTarget)); + }; + } forEach (_stateMachine getVariable TRANSITIONS(_thisState)); +}; + +#ifdef STATEMACHINE_PERFORMANCE_COUNTERS +private _perfRunTime = diag_tickTime - _perfStartTime; +(GVAR(performanceCounters) select _id) pushBack _perfRunTime; +#endif From 67de25e70e89ae5969d2ceeb0d69606478ebb6e9 Mon Sep 17 00:00:00 2001 From: Moritz Schmidt Date: Wed, 29 Jul 2020 13:37:16 +0200 Subject: [PATCH 2/3] use `select` over `if` --- addons/statemachine/fnc_createFromConfig.sqf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/statemachine/fnc_createFromConfig.sqf b/addons/statemachine/fnc_createFromConfig.sqf index a0a64ae2b..1c68fcaea 100644 --- a/addons/statemachine/fnc_createFromConfig.sqf +++ b/addons/statemachine/fnc_createFromConfig.sqf @@ -29,7 +29,7 @@ if (isNull _config) exitWith {}; private _list = compile getText (_config >> "list"); private _skipNull = (getNumber (_config >> "skipNull")) > 0; private _ticksPerFrame = getNumber (_config >> "ticksPerFrame"); -_ticksPerFrame = if (_ticksPerFrame == 0) then {1} else {_ticksPerFrame}; +_ticksPerFrame = [_ticksPerFrame, 1] select (_ticksPerFrame == 0); private _stateMachine = [_list, _skipNull, _ticksPerFrame] call FUNC(create); { From cc765ab0156c12aff40209514ce4f37830528c0d Mon Sep 17 00:00:00 2001 From: Moritz Schmidt Date: Wed, 29 Jul 2020 21:17:14 +0200 Subject: [PATCH 3/3] use `max` over `select` * fastest * prevents negative values. no going backwards in time for you! ;P --- addons/statemachine/fnc_createFromConfig.sqf | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/addons/statemachine/fnc_createFromConfig.sqf b/addons/statemachine/fnc_createFromConfig.sqf index 1c68fcaea..376fdb6c9 100644 --- a/addons/statemachine/fnc_createFromConfig.sqf +++ b/addons/statemachine/fnc_createFromConfig.sqf @@ -28,8 +28,7 @@ if (isNull _config) exitWith {}; private _list = compile getText (_config >> "list"); private _skipNull = (getNumber (_config >> "skipNull")) > 0; -private _ticksPerFrame = getNumber (_config >> "ticksPerFrame"); -_ticksPerFrame = [_ticksPerFrame, 1] select (_ticksPerFrame == 0); +private _ticksPerFrame = (getNumber (_config >> "ticksPerFrame")) max 1; private _stateMachine = [_list, _skipNull, _ticksPerFrame] call FUNC(create); {