├── LICENSE ├── README.md ├── cython ├── call_py_obj.pyx ├── cscm.pxd ├── py_obj_wrapper.hpp ├── readme.txt ├── scm.pyx ├── setup.py └── test_scm.py └── scm ├── CMakeLists.txt ├── FrameMover.cpp ├── FrameMover.h ├── Parallel.cpp ├── Parallel.h ├── RefCountObject.cpp ├── RefCountObject.h ├── State.cpp ├── State.h ├── StateMachine.cpp ├── StateMachine.h ├── StateMachineManager.cpp ├── StateMachineManager.h ├── tests ├── CMakeLists.txt ├── scm_tutorial.cpp ├── test-CandyMachine.cpp ├── test-HistoryMachine.cpp └── test-StateMachine.cpp └── uncopyable.h /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # scm 2 | 3 | This is a c++ library supporting statechart (in scxml format) originated by David Harel. 4 | Features like hierarchical states, parallel states, and history are ready for you to command. 5 | You won't find another one as easy and flexible to use as SCM. 6 | 7 | Just read this simple example. 8 | 9 | ``` 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | using namespace std; 16 | using namespace scm; 17 | 18 | std::string client_scxml = "\ 19 | \ 20 | \ 21 | \ 22 | \ 23 | \ 24 | \ 25 | \ 26 | \ 27 | \ 28 | \ 29 | \ 30 | ' \ 31 | \ 32 | "; 33 | 34 | class Life: public Uncopyable 35 | { 36 | StateMachine *mach_; 37 | 38 | public: 39 | Life() 40 | { 41 | mach_ = StateMachineManager::instance()->getMach("the life"); 42 | mach_->retain(); 43 | mach_->set_do_exit_state_on_destroy(true); 44 | REGISTER_STATE_SLOT (mach_, "appear", &Life::onentry_appear, &Life::onexit_appear, this); 45 | REGISTER_STATE_SLOT (mach_, "live", &Life::onentry_live, &Life::onexit_live, this); 46 | REGISTER_STATE_SLOT (mach_, "eat", &Life::onentry_eat, &Life::onexit_eat, this); 47 | REGISTER_STATE_SLOT (mach_, "move", &Life::onentry_move, &Life::onexit_move, this); 48 | REGISTER_STATE_SLOT (mach_, "dead", &Life::onentry_dead, &Life::onexit_dead, this); 49 | REGISTER_ACTION_SLOT(mach_, "say_hello", &Life::say_hello, this); 50 | mach_->StartEngine(); 51 | } 52 | 53 | ~Life () 54 | { 55 | mach_->release(); 56 | } 57 | 58 | void onentry_appear () 59 | { 60 | cout << "come to exist" << endl; 61 | } 62 | 63 | void onexit_appear() 64 | { 65 | cout << "we are going to..." << endl; 66 | } 67 | 68 | void onentry_live () 69 | { 70 | cout << "start living" << endl; 71 | } 72 | 73 | void onexit_live () 74 | { 75 | cout << "no longer live" << endl; 76 | } 77 | 78 | void onentry_eat () 79 | { 80 | cout << "start eating" << endl; 81 | } 82 | 83 | void onexit_eat () 84 | { 85 | cout << "stop eating" << endl; 86 | } 87 | 88 | void onentry_move () 89 | { 90 | cout << "start moving" << endl; 91 | } 92 | 93 | void onexit_move () 94 | { 95 | cout << "stop moving" << endl; 96 | } 97 | 98 | void onentry_dead () 99 | { 100 | cout << "end" << endl; 101 | } 102 | 103 | void onexit_dead () 104 | { 105 | assert (0 && "should not exit final state"); 106 | cout << "no, this won't get called." << endl; 107 | } 108 | 109 | void say_hello () 110 | { 111 | cout << "\n*** Hello, World! ***\n" << endl; 112 | } 113 | 114 | void test () 115 | { 116 | mach_->enqueEvent("born"); 117 | mach_->frame_move(0); // state change to 'live' 118 | mach_->enqueEvent("hp_zero"); 119 | mach_->frame_move(0); // state change to 'dead' 120 | } 121 | }; 122 | 123 | int main(int argc, char* argv[]) 124 | { 125 | AutoReleasePool apool; 126 | StateMachineManager::instance()->set_scxml("the life", client_scxml); 127 | { 128 | Life life; 129 | life.test (); 130 | } 131 | StateMachineManager::instance()->pumpMachEvents(); 132 | StateMachineManager::instance()->release_instance(); 133 | AutoReleasePool::pumpPools(); 134 | return 0; 135 | } 136 | ``` 137 | 138 | 139 | Build this and run it, you should see: 140 | """ 141 | come to exist 142 | we are going to... 143 | 144 | *** Hello, World! *** 145 | 146 | start living 147 | start eating 148 | start moving 149 | stop eating 150 | stop moving 151 | no longer live 152 | end 153 | """ 154 | 155 | Simply, 156 | 1. you can load the scxml from external file or from a string defined in your code. 157 | 2. you connect these onentry_ onexit_, etc. slots 158 | 3. you start the engine, call the framemove in main loop. 159 | Done. 160 | 161 | It's that easy! 162 | 163 | Read the tutorials at 164 | [English](http://zen747.blogspot.tw/2017/07/a-scm-framework-tutorial-statechart.html) 165 | 166 | [Traditional Chinese](http://zen747.blogspot.tw/2017/07/scm-framework.html) 167 | 168 | ``` 169 | -------------------------------------------------------------------------------- /cython/call_py_obj.pyx: -------------------------------------------------------------------------------- 1 | cdef public void call_py_obj(obj): 2 | obj() 3 | -------------------------------------------------------------------------------- /cython/cscm.pxd: -------------------------------------------------------------------------------- 1 | from libcpp.string cimport string 2 | from libcpp.vector cimport vector 3 | 4 | cdef extern from "py_obj_wrapper.hpp": 5 | cdef cppclass PyObjWrapper: 6 | PyObjWrapper() 7 | PyObjWrapper(pyobject) 8 | 9 | cdef extern from "scm/RefCountObject.h" namespace "scm": 10 | cdef cppclass RefCountObject: 11 | void retain() 12 | void release() 13 | void autorelease() 14 | int ref_count() const 15 | bint unique_ref() const 16 | 17 | cdef cppclass AutoReleasePool: 18 | AutoReleasePool() except + 19 | void pumpPool() 20 | @staticmethod 21 | void pumpPools() 22 | 23 | cdef extern from "scm/FrameMover.h" namespace "scm": 24 | cdef cppclass FrameMover(RefCountObject): 25 | void frame_move(float elapsed_time) 26 | void setPause (bint pause) 27 | void togglePause () 28 | void pause () 29 | void resume () 30 | inline bint paused () const 31 | inline double total_elapsed_time () const 32 | void reset_time () 33 | 34 | cdef extern from "scm/State.h" namespace "scm": 35 | cdef cppclass State(FrameMover): 36 | pass 37 | 38 | cdef extern from "scm/StateMachine.h" namespace "scm": 39 | cdef cppclass StateMachine(State): 40 | void StartEngine() 41 | void enqueEvent(const string event) 42 | void setActionSlot(const string action, PyObjWrapper) except + 43 | void set_do_exit_state_on_destroy(bint) 44 | const vector[string] get_all_states() const 45 | 46 | cdef extern from "scm/StateMachineManager.h" namespace "scm": 47 | cdef cppclass StateMachineManager: 48 | @staticmethod 49 | StateMachineManager * instance() except + 50 | @staticmethod 51 | void release_instance() 52 | StateMachineManager() except + 53 | StateMachine *getMach(const string scxml_id) 54 | void set_scxml(const string scxml_id, const string scxml_str) 55 | void pumpMachEvents() 56 | const vector[string] get_all_states(const string scxml_id) const 57 | -------------------------------------------------------------------------------- /cython/py_obj_wrapper.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "call_py_obj.h" 3 | 4 | class PyObjWrapper { 5 | PyObject * held_obj_; 6 | public: 7 | PyObjWrapper(PyObject *o) 8 | : held_obj_(o) 9 | { 10 | Py_XINCREF(o); 11 | } 12 | 13 | PyObjWrapper(const PyObjWrapper &rhs) 14 | : PyObjWrapper(rhs.held_obj_) 15 | { 16 | } 17 | 18 | PyObjWrapper(PyObjWrapper &rhs) 19 | : PyObjWrapper(rhs.held_obj_) 20 | { 21 | rhs.held_obj_ = 0; 22 | } 23 | 24 | PyObjWrapper():PyObjWrapper(nullptr) 25 | { 26 | } 27 | 28 | ~PyObjWrapper() 29 | { 30 | Py_XDECREF(held_obj_); 31 | } 32 | 33 | PyObjWrapper &operator=(const PyObjWrapper &rhs) 34 | { 35 | PyObjWrapper tmp = rhs; 36 | return (*this = std::move(tmp)); 37 | } 38 | 39 | PyObjWrapper &operator=(PyObjWrapper &rhs) 40 | { 41 | held_obj_ = rhs.held_obj_; 42 | rhs.held_obj_ = 0; 43 | return *this; 44 | } 45 | 46 | void operator()(void) 47 | { 48 | if (held_obj_) { 49 | call_py_obj(held_obj_); 50 | } 51 | } 52 | 53 | }; 54 | -------------------------------------------------------------------------------- /cython/readme.txt: -------------------------------------------------------------------------------- 1 | Here we try to use Cython to provide a Python interface. 2 | 3 | To install, 4 | python setup.py build_ext --inplace 5 | Test run, 6 | LD_LIBRARY_PATH=../lib:$LD_LIBRARY_PATH python3 test_scm.py 7 | -------------------------------------------------------------------------------- /cython/scm.pyx: -------------------------------------------------------------------------------- 1 | # distutils: language=c++ 2 | 3 | from cscm cimport * 4 | from libcpp.string cimport string 5 | 6 | cdef class PyAutoReleasePool: 7 | cdef AutoReleasePool *c_ptr 8 | def __cinit__(self): 9 | self.c_ptr = new AutoReleasePool() 10 | 11 | def __dealloc__(self): 12 | del self.c_ptr 13 | 14 | def pumpPool(self): 15 | self.c_ptr.pumpPool() 16 | 17 | @classmethod 18 | def pumpPools(self): 19 | AutoReleasePool.pumpPools() 20 | 21 | cdef class PyStateMachine: 22 | cdef StateMachine *c_ptr 23 | 24 | def __dealloc__(self): 25 | self.c_ptr.release() 26 | 27 | def StartEngine(self): 28 | self.c_ptr.StartEngine() 29 | 30 | def enqueEvent(self, event): 31 | self.c_ptr.enqueEvent(event.encode()) 32 | 33 | def setActionSlot(self, action, slot): 34 | self.c_ptr.setActionSlot(action.encode(), PyObjWrapper(slot)) 35 | 36 | def frame_move(self, elapsed_time): 37 | self.c_ptr.frame_move(elapsed_time) 38 | 39 | def set_do_exit_state_on_destroy(self, yesno): 40 | self.c_ptr.set_do_exit_state_on_destroy(yesno) 41 | 42 | def get_all_states(self): 43 | return self.c_ptr.get_all_states() 44 | 45 | @staticmethod 46 | cdef create(StateMachine *mach): 47 | py_mach = PyStateMachine() 48 | py_mach.c_ptr = mach 49 | py_mach.c_ptr.retain() 50 | return py_mach 51 | 52 | def register_state_slot(self, state, onentry, onexit): 53 | self.setActionSlot('onentry_'+state, onentry) 54 | self.setActionSlot('onexit_'+state, onexit) 55 | 56 | def register_handler(self, handler): 57 | states = self.get_all_states() 58 | for stateb in states: 59 | state = stateb.decode() 60 | s = state.replace('.', '_') 61 | try: 62 | self.setActionSlot('onentry_'+state, eval('handler.onentry_' + s)) 63 | except: 64 | pass 65 | try: 66 | self.setActionSlot('onexit_'+state, eval('handler.onexit_' + s)) 67 | except: 68 | pass 69 | 70 | _py_state_machine_manager_instance = None 71 | 72 | cdef class PyStateMachineManager: 73 | cdef StateMachineManager *c_ptr 74 | def __cinit__(self): 75 | self.c_ptr = new StateMachineManager() 76 | 77 | def __dealloc__(self): 78 | del self.c_ptr 79 | 80 | def getMach(self, scxml_id): 81 | cdef StateMachine *mach = self.c_ptr.getMach(scxml_id.encode()) 82 | return PyStateMachine.create(mach) 83 | 84 | def set_scxml(self, scxml_id, scxml_str): 85 | self.c_ptr.set_scxml(scxml_id.encode(), scxml_str.encode()) 86 | 87 | def pumpMachEvents(self): 88 | self.c_ptr.pumpMachEvents() 89 | 90 | @classmethod 91 | def instance(self): 92 | global _py_state_machine_manager_instance 93 | if not _py_state_machine_manager_instance: 94 | _py_state_machine_manager_instance = PyStateMachineManager() 95 | return _py_state_machine_manager_instance 96 | 97 | @classmethod 98 | def release_instance(self): 99 | _py_state_machine_manager_instance = None 100 | -------------------------------------------------------------------------------- /cython/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import Extension, setup 2 | from Cython.Build import cythonize 3 | 4 | ext_modules = [Extension("scm", sources=["scm.pyx", "call_py_obj.pyx"], libraries=["scm"], include_dirs=[".."], library_dirs=["../lib"]), ] 5 | 6 | setup( 7 | name='scm', 8 | ext_modules=cythonize(ext_modules, annotate=True), 9 | zip_safe=False, 10 | ) 11 | -------------------------------------------------------------------------------- /cython/test_scm.py: -------------------------------------------------------------------------------- 1 | from scm import * 2 | 3 | client_scxml = """\ 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | """ 22 | 23 | class Life: 24 | def __init__(self): 25 | self.mach_ = PyStateMachineManager.instance().getMach('the life') 26 | self.mach_.set_do_exit_state_on_destroy(True) 27 | # self.mach_.register_state_slot("appear", self.onentry_appear, self.onexit_appear) 28 | # self.mach_.register_state_slot("live", self.onentry_live, self.onexit_live) 29 | # self.mach_.register_state_slot("eat", self.onentry_eat, self.onexit_eat) 30 | # self.mach_.register_state_slot("move", self.onentry_move, self.onexit_move) 31 | # self.mach_.register_state_slot("dead", self.onentry_dead, self.onexit_dead) 32 | # self.mach_.register_state_slot("move.on", self.onentry_move_on, self.onexit_move_on) 33 | # Instead of register every entry/exit slot that follow this 'onentry_xxx', 'onexit_xxx' naming convetion, 34 | # You can simply use StateMachine.register_handler() for brevity, 35 | self.mach_.register_handler(self) 36 | # For other cases, use setActionSlot as usual. 37 | self.mach_.setActionSlot('say_hello', self.say_hello) 38 | print(">> let's start the engine") 39 | self.mach_.StartEngine() 40 | print(">> should enter the initial state appear") 41 | 42 | def test(self): 43 | self.mach_.enqueEvent("born") 44 | self.mach_.frame_move(0.001) # state change to 'live' 45 | # or 46 | #PyStateMachineManager.instance().pumpMachEvents() 47 | self.mach_.enqueEvent("hp_zero") 48 | #self.mach_.frame_move(0.001) # state change to 'dead' 49 | # or 50 | PyStateMachineManager.instance().pumpMachEvents() 51 | 52 | def onentry_appear(self): 53 | print("come to exist") 54 | 55 | def onexit_appear(self): 56 | print("we are going to...") 57 | 58 | def onentry_live(self): 59 | print("start living") 60 | 61 | def onexit_live(self): 62 | print("no longer live") 63 | 64 | def onentry_eat(self): 65 | print("start eating") 66 | 67 | def onexit_eat(self): 68 | print("stop eating") 69 | 70 | def onentry_move(self): 71 | print("start moving") 72 | 73 | def onexit_move(self): 74 | print("stop moving") 75 | 76 | def onentry_move_on(self): 77 | print("start move on") 78 | 79 | def onexit_move_on(self): 80 | print("stop move on") 81 | 82 | def onentry_dead(self): 83 | print("end") 84 | 85 | def onexit_dead(self): 86 | assert (0 and "should not exit final state"); 87 | print("no, this won't get called.") 88 | 89 | def say_hello(self): 90 | print("\n*** Hello, World! ***\n") 91 | 92 | 93 | if __name__ == '__main__': 94 | # Because of underlying c++ implementation, we have to declare an autorelease pool. 95 | pool = PyAutoReleasePool() 96 | PyStateMachineManager.instance().set_scxml("the life", client_scxml) 97 | 98 | life = Life() 99 | life.test() 100 | 101 | pool.pumpPools() 102 | del pool 103 | 104 | print("that's all, bye!") 105 | -------------------------------------------------------------------------------- /scm/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | # project name, statechart machine 3 | PROJECT(scm) 4 | 5 | FIND_PACKAGE(Boost REQUIRED) 6 | INCLUDE_DIRECTORIES(${Boost_INCLUDE_DIR}) 7 | 8 | # source files 9 | set (STATE_SRCS 10 | RefCountObject.cpp 11 | FrameMover.cpp 12 | StateMachineManager.cpp 13 | StateMachine.cpp 14 | Parallel.cpp 15 | State.cpp 16 | RefCountObject.h 17 | FrameMover.h 18 | StateMachineManager.h 19 | StateMachine.h 20 | Parallel.h 21 | State.h 22 | ) 23 | 24 | add_library(scm_static STATIC ${STATE_SRCS}) 25 | add_library(scm SHARED ${STATE_SRCS}) 26 | install (TARGETS scm_static DESTINATION lib) 27 | install (TARGETS scm DESTINATION lib) 28 | 29 | add_subdirectory(tests) 30 | -------------------------------------------------------------------------------- /scm/FrameMover.cpp: -------------------------------------------------------------------------------- 1 | #include "FrameMover.h" 2 | #include 3 | 4 | using namespace std; 5 | 6 | namespace scm { 7 | 8 | double FrameMover::system_move_time_; 9 | 10 | FrameMover::FrameMover () 11 | : total_elapsed_time_(0) 12 | , pause_(false) 13 | , frame_moving_(false) 14 | { 15 | } 16 | 17 | FrameMover::~FrameMover () 18 | { 19 | } 20 | 21 | void FrameMover::frame_move (float elapsed_seconds) 22 | { 23 | if (pause_) return; 24 | assert (!frame_moving_ && "Error! recursive frame_move() called"); 25 | total_elapsed_time_ += elapsed_seconds; 26 | frame_moving_ = true; 27 | signal_on_frame_move_(elapsed_seconds); 28 | this->onFrameMove (elapsed_seconds); 29 | frame_moving_ = false; 30 | } 31 | 32 | void FrameMover::setPause (bool p) 33 | { 34 | if (p != pause_) { 35 | if (p) { 36 | pause(); 37 | } else { 38 | resume(); 39 | } 40 | } 41 | } 42 | 43 | void FrameMover::togglePause () 44 | { 45 | if (pause_) { 46 | resume(); 47 | } else { 48 | pause(); 49 | } 50 | } 51 | 52 | void FrameMover::pause () 53 | { 54 | if (!pause_) { 55 | pause_ = true; 56 | signal_on_pause_(); 57 | onPause(); 58 | } 59 | } 60 | 61 | void FrameMover::resume () 62 | { 63 | if (pause_) { 64 | pause_ = false; 65 | signal_on_resume_(); 66 | onResume(); 67 | } 68 | } 69 | 70 | void FrameMover::reset_time () 71 | { 72 | total_elapsed_time_ = 0; 73 | } 74 | 75 | 76 | namespace { 77 | struct TimedActionTypeCompare 78 | { 79 | bool operator () (TimedActionType *lhs, TimedActionType *rhs) const 80 | { 81 | return lhs->time_ < rhs->time_; 82 | } 83 | }; 84 | 85 | std::list timed_acts_; 86 | 87 | } 88 | 89 | TimedActionType * PunctualFrameMover::registerTimedAction (float after_t, boost::function act, bool cancelable) 90 | { 91 | TimedActionType * p = new TimedActionType (after_t + system_move_time_, act, cancelable); 92 | 93 | list ::iterator it = std::upper_bound (timed_acts_.begin (), timed_acts_.end (), p, TimedActionTypeCompare ()); 94 | timed_acts_.insert (it, p); 95 | 96 | return p; 97 | } 98 | 99 | void PunctualFrameMover::clearTimedActions () 100 | { 101 | if (!timed_acts_.empty ()) { 102 | list ::iterator it = timed_acts_.begin (); 103 | list ::iterator it_end = timed_acts_.end (); 104 | for (; it != it_end ; ++it) { 105 | (*it)->release (); 106 | } 107 | timed_acts_.clear (); 108 | } 109 | } 110 | 111 | void PunctualFrameMover::pumpTimers (double t) 112 | { 113 | system_move_time_ += t; 114 | // handle timed action 115 | if (!timed_acts_.empty ()) { 116 | list ::iterator it = timed_acts_.begin (); 117 | for (; it != timed_acts_.end () ;) { 118 | if ((*it)->time_ <= system_move_time_) { 119 | if (!(*it)->cancelable_ || !(*it)->unique_ref ()) { 120 | (*it)->signal_ (); 121 | } 122 | (*it)->release (); 123 | it = timed_acts_.erase (it); 124 | } else { 125 | break; 126 | } 127 | } 128 | } 129 | } 130 | 131 | 132 | } 133 | 134 | -------------------------------------------------------------------------------- /scm/FrameMover.h: -------------------------------------------------------------------------------- 1 | #ifndef IFrameMover_H 2 | #define IFrameMover_H 3 | 4 | #include "RefCountObject.h" 5 | #include 6 | #include 7 | #include 8 | 9 | 10 | namespace bs2 = boost::signals2; 11 | 12 | namespace scm { 13 | 14 | class FrameMover: virtual public RefCountObject 15 | { 16 | protected: 17 | static double system_move_time_; 18 | 19 | protected: 20 | double total_elapsed_time_; 21 | bool pause_; 22 | bool frame_moving_; // for recursive frame_move() call check 23 | 24 | protected: 25 | 26 | virtual void onFrameMove (float elapsed_seconds) = 0; 27 | virtual void onPause() {} 28 | virtual void onResume() {} 29 | 30 | public: 31 | FrameMover (); 32 | virtual ~FrameMover(); 33 | 34 | virtual void frame_move (float elapsed_seconds); 35 | 36 | virtual void setPause (bool pause); 37 | virtual void togglePause (); 38 | virtual void pause (); 39 | virtual void resume (); 40 | inline bool paused () const { 41 | return pause_; 42 | } 43 | 44 | inline double total_elapsed_time () const { 45 | return this->total_elapsed_time_; 46 | } 47 | 48 | /** \brief reset total_elapsed_time to 0。*/ 49 | virtual void reset_time (); 50 | 51 | bs2::signal signal_on_frame_move_; 52 | bs2::signal signal_on_pause_; 53 | bs2::signal signal_on_resume_; 54 | }; 55 | 56 | /** 57 | PunctualFrameMover 讓使用者預訂未來某時間點呼叫某個動作。這邊以 signal/slot 的方式提供支援。 58 | PunctualFrameMover let user specify actions in the future. Support by utilize signal/slot mechanism. 59 | @see PunctualFrameMover::registerTimedAction () 60 | */ 61 | struct TimedActionType: public RefCountObject 62 | { 63 | typedef bs2::signal signal_t; 64 | double time_; 65 | signal_t signal_; 66 | bool cancelable_; 67 | bs2::scoped_connection conn_; 68 | 69 | TimedActionType (double time, boost::function slot, bool cancelable) 70 | :time_(time), cancelable_(cancelable) 71 | { 72 | conn_ = signal_.connect (slot); 73 | } 74 | 75 | ~TimedActionType () 76 | { 77 | } 78 | 79 | bool operator< (TimedActionType const&rhs) const 80 | { 81 | return time_ < rhs.time_; 82 | } 83 | }; 84 | 85 | class PunctualFrameMover: public FrameMover 86 | { 87 | /** 在 after_t 秒後,執行動作 act。 如果cancelable為真,reference count > 1才執行動作。 88 | * Execute act after after_t seconds, if cancelable is true, only perform action if reference count > 1. 89 | * i.e, if cancelable is true, you must retain and release later the returned object. 90 | */ 91 | static TimedActionType * registerTimedAction(float after_t, boost::function act, bool cancelable); 92 | 93 | public: 94 | // void return 95 | static void registerTimedAction(float after_t, boost::function act) { registerTimedAction(after_t, act, false); } 96 | // return reference counted object 97 | static TimedActionType * registerTimedAction_cancelable(float after_t, boost::function act) { return registerTimedAction(after_t, act, true); } 98 | 99 | /** 清除所有未執行動作 100 | * clear all timed actions 101 | */ 102 | static void clearTimedActions (); 103 | /** 104 | * move 105 | */ 106 | static void pumpTimers (double t); 107 | }; 108 | 109 | } 110 | 111 | #endif 112 | -------------------------------------------------------------------------------- /scm/Parallel.cpp: -------------------------------------------------------------------------------- 1 | #include "Parallel.h" 2 | #include "StateMachine.h" 3 | 4 | #include 5 | 6 | using std::string; 7 | using std::vector; 8 | 9 | namespace scm { 10 | 11 | const string done_state_prefix = "done.state."; 12 | 13 | ///////// 14 | Parallel::Parallel (std::string const& state_id, State* parent, StateMachine *machine) 15 | :State (state_id, parent, machine) 16 | { 17 | } 18 | 19 | Parallel *Parallel::clone (State *parent, StateMachine *m) 20 | { 21 | Parallel *pa = new Parallel (state_id_, parent, m); 22 | 23 | pa->clone_data (this); 24 | 25 | pa->autorelease (); 26 | 27 | return pa; 28 | } 29 | 30 | 31 | void Parallel::onEvent (string const &e) 32 | { 33 | if (this->isLeavingState ()) return; 34 | 35 | if (this->done_) { 36 | return; 37 | } 38 | 39 | if (e.substr(0, done_state_prefix.size()) == done_state_prefix) { 40 | string state_uid = e.substr (11); 41 | if (!state_uid.empty()) { 42 | this->finished_substates_.insert (state_uid); 43 | } 44 | } 45 | 46 | for (size_t i=0; i < transitions_.size (); ++i) { 47 | Transition const &tran = *transitions_[i]; 48 | if (tran.attr_->event_ == e) { 49 | bool change = trig_cond (tran); 50 | if (change) { 51 | this->changeState (tran); 52 | return; 53 | } 54 | } 55 | } 56 | 57 | for (size_t i=0; i < this->substates_.size (); ++i) { 58 | this->substates_[i]->onEvent (e); 59 | } 60 | 61 | // this state come to an end 62 | if (this->finished_substates_.size () == this->substates_.size ()) { 63 | done_ = true; 64 | this->signal_done (); 65 | machine_->enqueEvent (done_state_prefix + state_uid_); 66 | } 67 | } 68 | 69 | void Parallel::enterState (bool enter_substate) 70 | { 71 | if (active_) return; 72 | 73 | assert (!substates_.empty ()); 74 | 75 | if (parent_ && !machine_->history_type (parent_->state_uid()).empty ()) { 76 | parent_->history_state_id_ = this->state_uid(); 77 | } 78 | 79 | finished_substates_.clear (); 80 | 81 | done_ = false; 82 | active_ = true; 83 | this->reset_time (); 84 | 85 | machine_->current_enter_state_ = this; 86 | if (substates_.empty()) { 87 | machine_->leaf_states_.push_back(this); 88 | } 89 | 90 | signal_onentry (); 91 | if (this->parent_ && !this->parent_->inState (this, false)) { 92 | return; 93 | } 94 | 95 | if (enter_substate) { 96 | for (size_t i=0; i < this->substates_.size (); ++i) { 97 | this->substates_[i]->enterState (enter_substate); 98 | } 99 | } 100 | } 101 | 102 | void Parallel::doEnterState (std::vector &vps) 103 | { 104 | while (!vps.empty()) { 105 | State *state = vps.back (); 106 | 107 | if (state->depth_ < this->depth_) return; 108 | 109 | if (state->depth_ == this->depth_) { 110 | if (state == this) { 111 | vps.pop_back(); 112 | continue; 113 | } else { 114 | return; 115 | } 116 | } 117 | 118 | bool found = false; 119 | vps.pop_back (); 120 | for (size_t i=0; i < this->substates_.size(); ++i) { 121 | if (state == substates_[i]) { 122 | state->enterState (false); 123 | if (!vps.empty ()) { 124 | state->doEnterState (vps); 125 | } 126 | found = true; 127 | break; 128 | } 129 | } 130 | if (!found) { 131 | return; 132 | } 133 | } 134 | } 135 | 136 | void Parallel::exitState () 137 | { 138 | if (!active_) return; 139 | 140 | for (size_t i=0; i < this->substates_.size (); ++i) { 141 | substates_[i]->exitState (); 142 | } 143 | 144 | active_ = false; 145 | 146 | signal_onexit (); 147 | } 148 | 149 | bool Parallel::inState (std::string const& state_id, bool recursive) const 150 | { 151 | for (size_t i=0; i < this->substates_.size (); ++i) { 152 | if (substates_[i]->state_id_ == state_id) { 153 | return true; 154 | } 155 | } 156 | 157 | if (recursive) { 158 | for (size_t i=0; i < this->substates_.size (); ++i) { 159 | if (substates_[i]->inState (state_id, recursive)) { 160 | return true; 161 | } 162 | } 163 | } 164 | return false; 165 | } 166 | 167 | bool Parallel::inState (State const *state, bool recursive) const 168 | { 169 | for (size_t i=0; i < this->substates_.size (); ++i) { 170 | if (substates_[i] == state) { 171 | return true; 172 | } 173 | } 174 | 175 | if (recursive) { 176 | for (size_t i=0; i < this->substates_.size (); ++i) { 177 | if (substates_[i]->inState (state, recursive)) { 178 | return true; 179 | } 180 | } 181 | } 182 | return false; 183 | } 184 | 185 | void Parallel::onFrameMove (float t) 186 | { 187 | if (this->isLeavingState()) { 188 | if (this->check_leaving_state_finished (t)) return; 189 | } 190 | 191 | for (size_t i=0; i < this->substates_.size (); ++i) { 192 | this->substates_[i]->frame_move (t); 193 | if (!this->active_) return; 194 | } 195 | 196 | this->pumpNoEvents (); 197 | 198 | if (!this->active_) return; 199 | 200 | for (size_t i=0; i < frame_move_slots_.size(); ++i) { 201 | frame_move_slots_[i] (t); 202 | } 203 | } 204 | 205 | } 206 | 207 | -------------------------------------------------------------------------------- /scm/Parallel.h: -------------------------------------------------------------------------------- 1 | #ifndef Parallel_H 2 | #define Parallel_H 3 | 4 | #include "State.h" 5 | 6 | namespace scm { 7 | 8 | /** Parallel state 9 | * 以 scxml 為基礎。請參考 https://www.w3.org/TR/scxml/ 10 | * Based on scxml. please refer to https://www.w3.org/TR/scxml/ 11 | */ 12 | class Parallel: public State // a parallel region 13 | { 14 | public: 15 | Parallel (std::string const& state_id, State* parent, StateMachine *machine); 16 | 17 | 18 | virtual bool inState (std::string const& state_id, bool recursive=true) const; 19 | virtual bool inState (State const* state, bool recursive=true) const; 20 | 21 | virtual Parallel *clone (State *parent, StateMachine *m); 22 | 23 | protected: 24 | virtual void onEvent (std::string const &e); 25 | virtual void enterState (bool enter_substate=true); 26 | virtual void exitState (); 27 | virtual void doEnterState (std::vector &vps); 28 | virtual void onFrameMove (float t); 29 | 30 | 31 | std::set finished_substates_; 32 | 33 | }; 34 | 35 | } 36 | 37 | #endif 38 | -------------------------------------------------------------------------------- /scm/RefCountObject.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "RefCountObject.h" 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace std; 8 | 9 | namespace scm { 10 | 11 | RefCountObject::RefCountObject () 12 | :ref_count_(1) 13 | { 14 | } 15 | 16 | RefCountObject::~RefCountObject () 17 | { 18 | } 19 | 20 | void RefCountObject::retain () 21 | { 22 | ++ref_count_; 23 | } 24 | 25 | void RefCountObject::release () 26 | { 27 | assert (ref_count_ > 0); 28 | --ref_count_; 29 | if (ref_count_ == 0) { 30 | delete this; 31 | } 32 | } 33 | 34 | void RefCountObject::autorelease () 35 | { 36 | AutoReleasePool::currentPool().addToAutoreleasePool (this); 37 | } 38 | 39 | struct AutoReleasePool::PRIVATE 40 | { 41 | std::vector objs_; 42 | static std::vector pools_; 43 | }; 44 | 45 | std::vector AutoReleasePool::PRIVATE::pools_; 46 | 47 | AutoReleasePool::AutoReleasePool() 48 | { 49 | private_ = new PRIVATE; 50 | PRIVATE::pools_.push_back(this); 51 | } 52 | 53 | AutoReleasePool::~AutoReleasePool() 54 | { 55 | assert (PRIVATE::pools_.back() == this); 56 | this->pumpPool(); 57 | PRIVATE::pools_.pop_back(); 58 | delete private_; 59 | } 60 | 61 | void AutoReleasePool::addToAutoreleasePool(RefCountObject* obj) 62 | { 63 | assert (obj); 64 | assert (obj->ref_count () > 0); 65 | private_->objs_.push_back(obj); 66 | } 67 | 68 | AutoReleasePool& AutoReleasePool::currentPool() 69 | { 70 | return *PRIVATE::pools_.back(); 71 | } 72 | 73 | void AutoReleasePool::pumpPool() 74 | { 75 | std::vector objs; 76 | objs.swap(private_->objs_); 77 | for (size_t i=0; i < objs.size(); ++i) { 78 | objs[i]->release (); 79 | } 80 | } 81 | 82 | void AutoReleasePool::pumpPools() 83 | { 84 | for (size_t i=0; i < PRIVATE::pools_.size(); ++i) { 85 | PRIVATE::pools_[i]->pumpPool(); 86 | } 87 | } 88 | 89 | 90 | 91 | } 92 | 93 | -------------------------------------------------------------------------------- /scm/RefCountObject.h: -------------------------------------------------------------------------------- 1 | #ifndef RefCountObject_H 2 | #define RefCountObject_H 3 | 4 | namespace scm { 5 | /** RefcountObject 6 | * 關於如何使用retain(), release(), autrelease(), 請參考Apple的Objective-C文件。 7 | * Please refer to Apple's Objective-C document for how to use retain(), release(), and autorelease(). 8 | */ 9 | 10 | class RefCountObject 11 | { 12 | private: 13 | 14 | int ref_count_; 15 | 16 | public: 17 | RefCountObject (); 18 | /** \brief ref_count_ + 1 */ 19 | void retain (); 20 | /** \brief ref_count_ - 1。 若 ref_count_ 變成0會解構物件. If ref_count_ down to 0, object will be destructed. */ 21 | void release (); 22 | /** \brief 將物件加到 release queue 裏, 由AutoReleasePool管理。 Add object to release queue, managed by AutoReleasePool. */ 23 | void autorelease (); 24 | 25 | int ref_count () const { 26 | return ref_count_; 27 | } 28 | bool unique_ref () const { 29 | return ref_count_ == 1; 30 | } 31 | 32 | protected: 33 | virtual ~RefCountObject (); 34 | 35 | private: 36 | RefCountObject (RefCountObject const&rhs) {} 37 | RefCountObject &operator=(RefCountObject const&rhs) { 38 | return *this; 39 | } 40 | }; 41 | 42 | template class RefCountObjectGuard 43 | { 44 | T *obj_; 45 | 46 | public: 47 | T *operator-> () const { 48 | return obj_; 49 | } 50 | 51 | T &operator* () const { 52 | return *obj_; 53 | } 54 | 55 | RefCountObjectGuard(T *obj=0) 56 | :obj_(obj) 57 | { 58 | if (obj_) obj_->retain(); 59 | } 60 | 61 | ~RefCountObjectGuard() 62 | { 63 | if (obj_) obj_->release(); 64 | } 65 | 66 | RefCountObjectGuard(RefCountObjectGuard const&rhs) 67 | { 68 | if (rhs.obj_) rhs.obj_->retain(); 69 | obj_ = rhs.obj_; 70 | } 71 | 72 | RefCountObjectGuard &operator=(RefCountObjectGuard const&rhs) 73 | { 74 | if (rhs.obj_) rhs.obj_->retain(); 75 | if (obj_) obj_->release(); 76 | obj_ = rhs.obj_; 77 | return *this; 78 | } 79 | 80 | RefCountObjectGuard &operator=(T *obj) 81 | { 82 | if (obj) obj->retain(); 83 | if (obj_) obj_->release(); 84 | obj_ = obj; 85 | return *this; 86 | } 87 | 88 | void reset(T *obj=0) 89 | { 90 | if (obj) obj->retain(); 91 | if (obj_) obj_->release(); 92 | obj_ = obj; 93 | } 94 | 95 | T *get() const 96 | { 97 | return obj_; 98 | } 99 | }; 100 | 101 | typedef RefCountObjectGuard rco_guard; 102 | 103 | /** 呼叫了 autorelease 的物件會被放在這邊, 呼叫AutoReleasePool::pumpPools時無人reference的物件會被解構。 104 | * Objects invoked autorelease() will be placed in AutoReleasePool, when AutoReleasePool::pumpPools() invoked, objects without references will be destructed. 105 | */ 106 | class AutoReleasePool 107 | { 108 | public: 109 | void addToAutoreleasePool (RefCountObject *obj); 110 | void pumpPool (); 111 | 112 | AutoReleasePool (); 113 | ~AutoReleasePool (); 114 | 115 | static AutoReleasePool ¤tPool (); 116 | static void pumpPools (); 117 | 118 | private: 119 | struct PRIVATE; 120 | friend struct PRIVATE; 121 | PRIVATE *private_; 122 | 123 | }; 124 | 125 | } 126 | 127 | 128 | #endif 129 | -------------------------------------------------------------------------------- /scm/State.cpp: -------------------------------------------------------------------------------- 1 | #include "State.h" 2 | #include "StateMachine.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | using namespace std; 12 | 13 | namespace scm { 14 | 15 | const string done_state_prefix = "done.state."; 16 | extern size_t splitStringToVector (std::string const &valstr, std::vector &val, size_t max_s=0xffffffff, std::string const&separators=", \t"); 17 | 18 | struct State::PRIVATE 19 | { 20 | State *self_; 21 | Transition *leaving_target_transition_; 22 | float leaving_delay_; 23 | float leaving_elapsed_seconds_; 24 | 25 | PRIVATE (State *state) 26 | : self_(state) 27 | , leaving_delay_(0) 28 | , leaving_elapsed_seconds_(0) 29 | , leaving_target_transition_(0) 30 | { 31 | } 32 | 33 | ~PRIVATE () 34 | { 35 | } 36 | 37 | void connect_transitions_signal (std::vector > &transitions); 38 | void connect_transitions_conds (std::vector > &transitions); 39 | }; 40 | 41 | 42 | State::State (std::string const& state_id, State* parent, StateMachine *machine) 43 | : parent_(parent), current_state_(0), machine_(machine), depth_(0) 44 | , is_a_final_(false), done_(false), active_(false), is_unique_state_id_(true), slots_ready_(false) 45 | { 46 | private_ = new PRIVATE(this); 47 | 48 | if (parent_) { 49 | this->depth_ = parent_->depth_ + 1; 50 | } 51 | 52 | set_state_id (state_id); 53 | 54 | } 55 | 56 | State::~State () 57 | { 58 | if (machine_) machine_->removeState (this); 59 | this->transitions_.clear (); 60 | this->no_event_transitions_.clear (); 61 | delete private_; 62 | } 63 | 64 | void State::set_state_id(const string& id) 65 | { 66 | assert (!slots_ready_ && "slots ready, can't change state uid!"); 67 | if (slots_ready_) { 68 | return; 69 | } 70 | 71 | if (!state_id_.empty() && machine_ != this) { 72 | machine_->removeState(this); 73 | } 74 | 75 | state_id_ = id; 76 | 77 | if (state_id_.empty()) { // anonymous state 78 | if (machine_ == this) { 79 | state_id_ = "_root"; 80 | } else { 81 | ostringstream stream; 82 | stream << "_st" << machine_->num_of_states();; 83 | state_id_ = stream.str(); 84 | } 85 | } else { 86 | is_unique_state_id_ = machine_->is_unique_id(state_id_); 87 | } 88 | 89 | if (this->is_unique_state_id_) { 90 | state_uid_ = this->state_id_; 91 | } else { 92 | state_uid_ = parent_->state_uid() + "." + this->state_id_; 93 | } 94 | 95 | if (machine_ != this) { // delay addState to after machine_ construction complete. 96 | machine_->addState (this); 97 | } 98 | } 99 | 100 | 101 | State *State::clone (State *parent, StateMachine *m) 102 | { 103 | State *state = new State (state_id_, parent, m); 104 | 105 | state->clone_data (this); 106 | 107 | state->autorelease (); 108 | 109 | return state; 110 | } 111 | 112 | void State::clone_data (State *rhs) 113 | { 114 | for (size_t i=0; i < rhs->substates_.size (); ++i) { 115 | substates_.push_back (rhs->substates_[i]->clone (this, machine_)); 116 | substates_.back ()->retain (); 117 | } 118 | depth_ = rhs->depth_; 119 | is_a_final_ = rhs->is_a_final_; 120 | private_->leaving_delay_ = rhs->private_->leaving_delay_; 121 | is_unique_state_id_ = rhs->is_unique_state_id_; 122 | } 123 | 124 | void State::machine_clear_substates () 125 | { 126 | for (size_t i=0; i < substates_.size (); ++i) { 127 | substates_[i]->machine_clear_substates (); 128 | substates_[i]->release(); 129 | } 130 | 131 | machine_ = 0; 132 | substates_.clear (); 133 | } 134 | 135 | void State::reset_history () 136 | { 137 | if (!machine_) return; 138 | 139 | if (!machine_->with_history_) return; 140 | 141 | if (!machine_->history_type(this->state_uid()).empty()) { 142 | this->clearHistory (); 143 | } 144 | 145 | for (size_t i=0; i < substates_.size (); ++i) { 146 | substates_[i]->reset_history (); 147 | } 148 | } 149 | 150 | bool State::trig_cond (Transition const &tran) const 151 | { 152 | if (tran.cond_functor_) { 153 | bool change = tran.cond_functor_(); 154 | if (tran.attr_->not_) change = !change; 155 | return change; 156 | } else if (!tran.attr_->in_state_.empty ()) { 157 | bool change = false; 158 | for (size_t i=0; i < tran.attr_->in_state_.size(); ++i) { 159 | change |= machine_->inState (tran.attr_->in_state_[i]); 160 | } 161 | if (tran.attr_->not_) change = !change; 162 | return change; 163 | } else { 164 | return true; 165 | } 166 | } 167 | 168 | void State::enterState (bool enter_substate) 169 | { 170 | if (active_) return; 171 | 172 | if (parent_ && !machine_->history_type (parent_->state_uid()).empty ()) { 173 | parent_->history_state_id_ = this->state_uid(); 174 | } 175 | 176 | this->reset_time (); 177 | 178 | this->done_ = false; 179 | active_ = true; 180 | 181 | machine_->current_enter_state_ = this; 182 | if (substates_.empty()) { 183 | machine_->leaf_states_.push_back(this); 184 | } 185 | 186 | signal_onentry (); 187 | 188 | if (!this->active_) { // in case state changed immediately at last signal_onentry. 189 | return; 190 | } 191 | 192 | if (enter_substate) { 193 | doEnterSubState(); 194 | } 195 | 196 | if (is_a_final_ && parent_) { 197 | parent_->done_ = true; 198 | parent_->signal_done (); 199 | machine_->enqueEvent (done_state_prefix + parent_->state_uid()); 200 | } 201 | } 202 | 203 | void State::exitState () 204 | { 205 | if (!active_) return; 206 | 207 | if (this->current_state_) { 208 | this->current_state_->exitState (); 209 | } 210 | 211 | active_ = false; 212 | this->current_state_ = 0; 213 | 214 | signal_onexit (); 215 | } 216 | 217 | void State::onEvent (string const &e) 218 | { 219 | if (this->done_) { 220 | return; 221 | } 222 | 223 | if (this->isLeavingState()) { 224 | return; 225 | } 226 | 227 | for (size_t i=0; i < transitions_.size (); ++i) { 228 | Transition const &tran = *transitions_[i]; 229 | if (tran.attr_->event_ == e) { 230 | bool change = trig_cond (tran); 231 | if (change) { 232 | this->changeState (tran); 233 | return; 234 | } 235 | } 236 | } 237 | 238 | if (this->current_state_) { 239 | this->current_state_->onEvent (e); 240 | } 241 | } 242 | 243 | string State::initial_state() const 244 | { 245 | string const&inits = machine_->initial_state_of_state(state_uid()); 246 | if (!inits.empty()) { 247 | return inits; 248 | } else if (!substates_.empty()) { 249 | return substates_[0]->state_uid(); 250 | } else { 251 | return ""; // a leaf state has not initial. 252 | } 253 | } 254 | 255 | 256 | State* State::findState(const string& state_id, const State *exclude, bool check_parent) const 257 | { 258 | for (size_t i=0; i < substates_.size(); ++i) { 259 | if (substates_[i]->state_id () == state_id) { 260 | return substates_[i]; 261 | } 262 | } 263 | 264 | for (size_t i=0; i < substates_.size(); ++i) { 265 | if (substates_[i] != exclude) { 266 | State *st = substates_[i]->findState(state_id, exclude, false); 267 | if (st) return st; 268 | } 269 | } 270 | 271 | if (check_parent && parent_) { 272 | State *st = parent_->findState(state_id, this, true); 273 | if (st) return st; 274 | } 275 | 276 | return 0; 277 | } 278 | 279 | float State::leavingDelay () const 280 | { 281 | return private_->leaving_delay_; 282 | } 283 | 284 | bool State::isLeavingState () const 285 | { 286 | return private_->leaving_target_transition_; 287 | } 288 | 289 | void State::setLeavingDelay (float delay) 290 | { 291 | private_->leaving_delay_ = delay; 292 | } 293 | 294 | void State::clearHistory () 295 | { 296 | this->history_state_id_ = ""; 297 | } 298 | 299 | void State::clearDeepHistory () 300 | { 301 | this->history_state_id_ = ""; 302 | for (size_t i=0; i < this->substates_.size (); ++i) { 303 | this->substates_[i]->clearDeepHistory (); 304 | } 305 | } 306 | 307 | void State::changeState (Transition const &transition) 308 | { 309 | std::string target; 310 | if (!transition.attr_->random_target_.empty ()) { 311 | int index = rand()%transition.attr_->random_target_.size (); 312 | target = transition.attr_->random_target_[index]; 313 | } else { 314 | target = transition.attr_->transition_target_; 315 | } 316 | 317 | std::string const& corresponding_state = machine_->state_id_of_history(target); 318 | if (!corresponding_state.empty()) { 319 | State *st = machine_->getState (corresponding_state); 320 | std::string const& h = st->history_state_id_; 321 | if (!h.empty()) { 322 | target = h; 323 | } else { 324 | target = st->initial_state (); 325 | } 326 | } 327 | 328 | machine_->transition_source_state_ = this->state_uid(); 329 | machine_->transition_target_state_ = target; 330 | 331 | if (!private_->leaving_target_transition_) { 332 | if (private_->leaving_delay_ != 0) { 333 | TransitionAttr *attr = new TransitionAttr (transition.attr_->event_, target); 334 | private_->leaving_target_transition_ = new Transition(attr); 335 | private_->leaving_target_transition_->setAttr (attr); 336 | attr->release (); 337 | if (!transition.attr_->ontransit_.empty ()) { 338 | boost::function s; 339 | if (this->machine_->GetActionSlot (transition.attr_->ontransit_, s)) { 340 | private_->leaving_target_transition_->signal_transit.connect (s); 341 | } 342 | } 343 | return; 344 | } 345 | } 346 | 347 | vector newState; 348 | 349 | if (target.find(',') == string::npos) { 350 | State *st = machine_->getState (target); 351 | if (st) { 352 | newState.push_back(st); 353 | } else { 354 | assert (0 && "can't find transition target state!"); 355 | } 356 | } else { 357 | vector targets; 358 | splitStringToVector(target, targets); 359 | for (size_t i=0; i < targets.size(); ++i) { 360 | State *st = machine_->getState (targets[i]); 361 | if (st) { 362 | newState.push_back(st); 363 | } else { 364 | assert (0 && "can't find transition target state!"); 365 | } 366 | } 367 | } 368 | 369 | if (newState.empty()) { 370 | assert (0 && "can't find transition target state!"); 371 | return; 372 | } 373 | 374 | State *lcaState = this->findLCA (newState[0]); 375 | 376 | if (!lcaState) { 377 | assert (lcaState && "can't find common ancestor state."); 378 | return; 379 | } 380 | 381 | // make sure they are in the same parallel state? 382 | if (newState.size() > 1) { 383 | for (size_t i=1; i < newState.size(); ++i) { 384 | State *lca = this->findLCA (newState[i]); 385 | if (lca != lcaState) { 386 | assert (lca && "multiple targets but can't find common ancestor."); 387 | return; 388 | } 389 | } 390 | } 391 | 392 | machine_->leaf_states_.clear(); 393 | 394 | // exit old states 395 | if (newState.size() == 1 && lcaState == newState[0]) { // for reentering 396 | machine_->transition_source_state_ = lcaState->state_uid(); 397 | lcaState->exitState (); 398 | transition.signal_transit (); 399 | lcaState->enterState (); 400 | return; 401 | } else if (lcaState->current_state_) { 402 | machine_->transition_source_state_ = lcaState->current_state_->state_uid(); 403 | lcaState->current_state_->exitState (); 404 | } 405 | 406 | // transition 407 | transition.signal_transit (); 408 | 409 | // enter new state 410 | vector vps; 411 | for (size_t i=0; i < newState.size(); ++i) { 412 | vps.push_back (newState[i]); 413 | State *parentstate = newState[i]->parent_; 414 | while (parentstate && (parentstate != lcaState)) { 415 | vps.push_back (parentstate); 416 | parentstate = parentstate->parent_; 417 | } 418 | } 419 | 420 | lcaState->doEnterState (vps); 421 | } 422 | 423 | void State::doEnterState (std::vector &vps) 424 | { 425 | State *state = vps.back (); 426 | if (state->depth_ <= this->depth_) return; 427 | 428 | vps.pop_back (); 429 | this->current_state_ = state; 430 | bool enter_subst= vps.empty () || vps.back()->depth_ <= current_state_->depth_; 431 | this->current_state_->enterState (enter_subst); 432 | if (!vps.empty ()) { 433 | this->current_state_->doEnterState (vps); 434 | } 435 | } 436 | 437 | void State::doEnterSubState() 438 | { 439 | if (!this->history_state_id_.empty() && !machine_->history_type(this->state_uid()).empty()) { 440 | this->current_state_ = machine_->getState (this->history_state_id_); 441 | this->current_state_->enterState (); 442 | } else { 443 | if (!substates_.empty ()) { 444 | const string &initial_state = machine_->initial_state_of_state(this->state_uid()); 445 | if (initial_state.empty ()) { 446 | this->current_state_ = this->substates_.front (); 447 | this->current_state_->enterState (); 448 | } else { 449 | TransitionAttr *attr = new TransitionAttr ("", initial_state); 450 | Transition tran(attr); 451 | attr->release (); 452 | this->changeState (tran); 453 | } 454 | } 455 | } 456 | } 457 | 458 | 459 | State *State::findLCA (State const* ots) 460 | { 461 | assert (ots && "State::findLCA on invalid state object"); 462 | if (this == ots) { 463 | return this; 464 | } else if (this->depth_ > ots->depth_) { 465 | return this->parent_->findLCA (ots); 466 | } else if (this->depth_ < ots->depth_) { 467 | return this->findLCA (ots->parent_); 468 | } else { 469 | if (this->parent_ == ots->parent_) { 470 | return this->parent_; 471 | } else { 472 | return this->parent_->findLCA (ots->parent_); 473 | } 474 | } 475 | 476 | return 0; 477 | } 478 | 479 | bool State::inState (std::string const& state_id, bool recursive) const 480 | { 481 | if (!current_state_) return false; 482 | if (current_state_->state_id_ == state_id) { 483 | return true; 484 | } else if (recursive) { 485 | return current_state_->inState (state_id, recursive); 486 | } else { 487 | return false; 488 | } 489 | } 490 | 491 | bool State::inState (State const*state, bool recursive) const 492 | { 493 | if (!current_state_) return false; 494 | if (current_state_ == state) { 495 | return true; 496 | } else if (recursive) { 497 | return current_state_->inState (state, recursive); 498 | } else { 499 | return false; 500 | } 501 | } 502 | 503 | void State::prepareActionCondSlots () 504 | { 505 | if (this->slots_ready_) { 506 | return; 507 | } 508 | 509 | this->slots_ready_ = true; 510 | 511 | std::vector const&tran_attrs = machine_->transition_attr(this->state_uid()); 512 | size_t size = tran_attrs.size (); 513 | transitions_.reserve(size); 514 | no_event_transitions_.reserve(size); 515 | for (size_t i=0; i < tran_attrs.size(); ++i) { 516 | boost::shared_ptr ptr (new Transition(tran_attrs[i])); 517 | if (tran_attrs[i]->event_.empty()) { 518 | no_event_transitions_.push_back (ptr); 519 | } else { 520 | transitions_.push_back (ptr); 521 | } 522 | } 523 | std::vector > (transitions_).swap(transitions_); 524 | std::vector > (no_event_transitions_).swap(no_event_transitions_); 525 | 526 | // add clear history action 527 | this->machine_->setActionSlot ("clh(" + state_uid() + "*)", boost::bind(&State::clearDeepHistory, this)); 528 | this->machine_->setActionSlot ("clh(" + state_uid() + ")", boost::bind(&State::clearHistory, this)); 529 | 530 | for (size_t i=0; i < this->substates_.size (); ++i) { 531 | this->substates_[i]->prepareActionCondSlots (); 532 | } 533 | 534 | } 535 | 536 | void State::connectCondSlots () 537 | { 538 | private_->connect_transitions_conds(transitions_); 539 | private_->connect_transitions_conds(no_event_transitions_); 540 | 541 | for (size_t i=0; i < this->substates_.size (); ++i) { 542 | this->substates_[i]->connectCondSlots (); 543 | } 544 | } 545 | 546 | void State::PRIVATE::connect_transitions_conds(vector< boost::shared_ptr< Transition > >& transitions) 547 | { 548 | for (size_t i=0; i < transitions.size (); ++i) { 549 | if (!transitions[i]->attr_->cond_.empty ()) { 550 | std::string cond = transitions[i]->attr_->cond_; 551 | std::string instate_check = cond.substr (0, 3); 552 | if (instate_check == "In(" || instate_check == "in(") { 553 | string::size_type endmark = cond.find_first_of (')', 4); 554 | string st = cond.substr (3, endmark - 3); 555 | vector state_list; 556 | size_t len = st.length(); 557 | size_t start_pos = 0; 558 | for (size_t si=0; si < len; ++si) { 559 | if (st[si] == '|') { 560 | if (si-start_pos > 0) { 561 | state_list.push_back(st.substr(start_pos,si-start_pos)); 562 | start_pos = si+1; 563 | } 564 | } 565 | } 566 | state_list.push_back(st.substr(start_pos)); 567 | 568 | for (size_t si=0; si < state_list.size(); ++si) { 569 | if (!self_->machine_->is_unique_id(state_list[si])) { 570 | State *s = self_->findState(state_list[si]); 571 | if (!s) { 572 | assert (0 && "can't find state for In() check."); 573 | continue; 574 | } 575 | state_list[si] = s->state_uid(); 576 | } else { 577 | State *s = self_->findState(state_list[si]); 578 | if (!s) { 579 | assert (0 && "can't find state for In() check."); 580 | continue; 581 | } 582 | } 583 | } 584 | transitions[i]->attr_->in_state_ = state_list; 585 | } else { 586 | boost::function s; 587 | if (self_->machine_->GetCondSlot (cond, s)) { 588 | transitions[i]->cond_functor_ = s; 589 | } else { 590 | assert (0 && "can't connect cond slot"); 591 | } 592 | } 593 | } 594 | } 595 | 596 | } 597 | 598 | 599 | void State::connectActionSlots () 600 | { 601 | boost::function s; 602 | string const&onentry = machine_->onentry_action(this->state_uid()); 603 | if (!onentry.empty ()) { 604 | if (this->machine_->GetActionSlot (onentry, s)) { 605 | this->signal_onentry.connect (s); 606 | } else if (onentry != "onentry_"+state_uid()) { 607 | assert (0 && "can't connect onentry slot"); 608 | } 609 | } 610 | 611 | string const&onexit = machine_->onexit_action(this->state_uid()); 612 | if (!onexit.empty ()) { 613 | if (this->machine_->GetActionSlot (onexit, s)) { 614 | this->signal_onexit.connect (s); 615 | } else if (onexit != "onexit_"+state_uid()) { 616 | assert (0 && "can't connect onexit slot"); 617 | } 618 | } 619 | 620 | string const &frame_move = machine_->frame_move_action(this->state_uid()); 621 | if (!frame_move.empty ()) { 622 | boost::function sf; 623 | if (this->machine_->GetFrameMoveSlot (frame_move, sf)) { 624 | frame_move_slots_.push_back(sf); 625 | } else if (frame_move != state_uid()) { // specified frame_move slot but not found 626 | assert (0 && "can't connect frame_move slot"); 627 | } 628 | } 629 | 630 | private_->connect_transitions_signal (transitions_); 631 | private_->connect_transitions_signal (no_event_transitions_); 632 | 633 | for (size_t i=0; i < this->substates_.size (); ++i) { 634 | this->substates_[i]->connectActionSlots (); 635 | } 636 | } 637 | 638 | void State::PRIVATE::connect_transitions_signal(vector< boost::shared_ptr< Transition > >& transitions) 639 | { 640 | for (size_t i=0; i < transitions.size (); ++i) { 641 | if (!transitions[i]->attr_->ontransit_.empty ()) { 642 | string const&tr = transitions[i]->attr_->ontransit_; 643 | boost::function s; 644 | if (self_->machine_->GetActionSlot (tr, s)) { 645 | transitions[i]->signal_transit.connect (s); 646 | } else { 647 | assert (0 && "can't connect on_transit slot"); 648 | } 649 | } 650 | } 651 | } 652 | 653 | void State::doLeaveAferDelay () 654 | { 655 | if (private_->leaving_target_transition_) { 656 | // change state 657 | this->changeState (*private_->leaving_target_transition_); 658 | delete private_->leaving_target_transition_; 659 | private_->leaving_target_transition_ = 0; 660 | private_->leaving_elapsed_seconds_ = 0; 661 | } 662 | } 663 | 664 | bool State::check_leaving_state_finished(float t) 665 | { 666 | if (private_->leaving_delay_ < 0) return false; 667 | private_->leaving_elapsed_seconds_ += t; 668 | if (private_->leaving_elapsed_seconds_ >= private_->leaving_delay_) { 669 | doLeaveAferDelay (); 670 | return true; 671 | } 672 | return false; 673 | } 674 | 675 | void State::onFrameMove (float t) 676 | { 677 | if (this->isLeavingState()) { 678 | if (this->check_leaving_state_finished (t)) return; 679 | } 680 | 681 | if (this->current_state_) { 682 | this->current_state_->frame_move (t); 683 | if (!this->active_) return; 684 | } 685 | 686 | this->pumpNoEvents (); 687 | 688 | if (!this->active_) return; 689 | 690 | for (size_t i=0; i < frame_move_slots_.size(); ++i) { 691 | frame_move_slots_[i] (t); 692 | } 693 | } 694 | 695 | void State::pumpNoEvents() 696 | { 697 | if (this->isLeavingState()) return; 698 | 699 | for (size_t i=0; i < no_event_transitions_.size (); ++i) { 700 | Transition const &tran = *no_event_transitions_[i]; 701 | if (trig_cond (tran)) { 702 | this->changeState (tran); 703 | return; 704 | } 705 | } 706 | } 707 | 708 | } 709 | 710 | -------------------------------------------------------------------------------- /scm/State.h: -------------------------------------------------------------------------------- 1 | #ifndef State_H 2 | #define State_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "RefCountObject.h" 10 | #include "FrameMover.h" 11 | 12 | namespace scm { 13 | 14 | class StateMachine; 15 | 16 | struct TransitionAttr: public RefCountObject 17 | { 18 | std::string event_; 19 | std::string transition_target_; 20 | std::vector random_target_; 21 | std::string cond_; // for later connecting slot 22 | std::string ontransit_; // for later connecting slot 23 | std::vector in_state_; // for inState check if not empty. You can use '|' to specify multiple states, ex. "In(state1|state2)" 24 | bool not_; // to support "!In(state)" 25 | 26 | TransitionAttr (std::string const &e, std::string const &t) 27 | : event_(e), transition_target_(t), not_(false) 28 | { 29 | } 30 | }; 31 | 32 | struct Transition 33 | { 34 | TransitionAttr *attr_; 35 | boost::function cond_functor_; 36 | bs2::signal signal_transit; 37 | 38 | Transition (TransitionAttr *attr) 39 | : attr_(attr) 40 | { 41 | attr->retain(); 42 | } 43 | 44 | 45 | ~Transition () 46 | { 47 | if (attr_) attr_->release (); 48 | attr_ = 0; 49 | } 50 | 51 | void setAttr (TransitionAttr *attr) 52 | { 53 | attr->retain (); 54 | if (attr_) attr_->release (); 55 | attr_ = attr; 56 | } 57 | }; 58 | 59 | /** State 60 | 以 scxml 為基礎。請參考 https://www.w3.org/TR/scxml/ 61 | Based on scxml, please refer to https://www.w3.org/TR/scxml/ 62 | */ 63 | 64 | class State: public FrameMover 65 | { 66 | protected: 67 | StateMachine *machine_; 68 | State *parent_; 69 | State *current_state_; 70 | char depth_; 71 | bool is_a_final_; 72 | bool done_; 73 | bool slots_ready_; 74 | 75 | bool active_; 76 | bool is_unique_state_id_; 77 | 78 | std::string state_id_; 79 | std::string state_uid_; 80 | 81 | std::vector substates_; 82 | std::string history_state_id_; 83 | 84 | std::vector > no_event_transitions_; 85 | std::vector > transitions_; 86 | 87 | std::vector > frame_move_slots_; 88 | 89 | 90 | public: 91 | // signals 92 | bs2::signal signal_done; 93 | bs2::signal signal_onentry; 94 | bs2::signal signal_onexit; 95 | 96 | public: 97 | State (std::string const& state_id, State* parent, StateMachine *machine); 98 | 99 | virtual ~State (); 100 | 101 | void machine_clear_substates (); 102 | 103 | void set_state_id (std::string const&id); 104 | 105 | inline std::string const & state_id () const 106 | { 107 | return state_id_; 108 | } 109 | 110 | inline std::string const & state_uid() const 111 | { 112 | return state_uid_; 113 | } 114 | 115 | virtual State *clone (State *parent, StateMachine *m); 116 | void clone_data (State *rhs); 117 | 118 | inline bool done () const { 119 | return done_; 120 | } 121 | inline bool active () const { 122 | return active_; 123 | } 124 | inline char depth () const { 125 | return depth_; 126 | } 127 | 128 | std::string initial_state () const; 129 | State * findState(std::string const&state_id, const State *exclude=0, bool check_parent=true) const; 130 | 131 | float leavingDelay () const; 132 | bool isLeavingState () const; 133 | bool check_leaving_state_finished (float t); 134 | // set to < 0 for indefinite delay 135 | void setLeavingDelay (float delay); 136 | 137 | virtual void clearHistory (); 138 | virtual void clearDeepHistory (); 139 | 140 | virtual void changeState (Transition const &transition); 141 | virtual void doEnterState (std::vector &vps); 142 | virtual void doEnterSubState (); 143 | 144 | virtual bool inState (std::string const& state_id, bool recursive=true) const; 145 | virtual bool inState (State const* state, bool recursive=true) const; 146 | 147 | friend class Parallel; 148 | friend class StateMachine; 149 | friend class StateMachineManager; 150 | 151 | protected: 152 | virtual void onEvent (std::string const &e); 153 | virtual void enterState (bool enter_substate=true); 154 | virtual void exitState (); 155 | 156 | void doLeaveAferDelay (); 157 | 158 | State *findLCA (State const* ots); 159 | 160 | virtual void prepareActionCondSlots (); 161 | virtual void connectCondSlots (); 162 | virtual void connectActionSlots (); 163 | 164 | virtual void onFrameMove (float t); 165 | 166 | void pumpNoEvents (); 167 | 168 | virtual void reset_history (); 169 | 170 | bool trig_cond (Transition const &tran) const; 171 | 172 | private: 173 | struct PRIVATE; 174 | friend struct PRIVATE; 175 | PRIVATE *private_; 176 | 177 | }; 178 | 179 | } 180 | 181 | 182 | #endif 183 | -------------------------------------------------------------------------------- /scm/StateMachine.cpp: -------------------------------------------------------------------------------- 1 | #include "StateMachine.h" 2 | #include "StateMachineManager.h" 3 | #include 4 | #include 5 | #include 6 | using namespace std; 7 | 8 | namespace scm { 9 | 10 | namespace { 11 | struct StateTimedEventTypeCmpP 12 | { 13 | bool operator () (TimedEventType *lhs, TimedEventType *rhs) const 14 | { 15 | return lhs->time_ < rhs->time_; 16 | } 17 | }; 18 | } 19 | 20 | 21 | struct StateMachine::PRIVATE 22 | { 23 | StateMachine *mach_; 24 | std::list timed_events_; 25 | std::vector queued_events_; 26 | 27 | PRIVATE(StateMachine *mach) 28 | : mach_(mach) 29 | { 30 | } 31 | 32 | ~PRIVATE () 33 | { 34 | } 35 | 36 | void loadSCXMLString (std::string const&xmlstr); 37 | 38 | }; 39 | 40 | StateMachine::StateMachine (StateMachineManager *manager) 41 | : super ("", 0, this) 42 | , manager_(manager) 43 | , slots_prepared_(false) 44 | , slots_connected_(false) 45 | , scxml_loaded_(false) 46 | , engine_started_(false) 47 | , on_event_(false) 48 | , with_history_(false) 49 | , current_enter_state_(0) 50 | , frame_move_slots_(0) 51 | , cond_slots_(0) 52 | , action_slots_(0) 53 | , allow_nop_entry_exit_slot_(false) 54 | , do_exit_state_on_destroy_(false) 55 | { 56 | private_ = new PRIVATE(this); 57 | this->addState (this); 58 | } 59 | 60 | StateMachine::~StateMachine () 61 | { 62 | this->clearTimedEvents (); 63 | destroy_machine (do_exit_state_on_destroy_); 64 | delete private_; 65 | } 66 | 67 | StateMachine* StateMachine::clone () 68 | { 69 | StateMachine *mach = new StateMachine (manager_); 70 | mach->autorelease (); 71 | 72 | mach->state_id_ = this->state_id_; 73 | mach->scxml_id_ = this->scxml_id_; 74 | mach->scxml_loaded_ = this->scxml_loaded_; 75 | 76 | mach->machine_ = mach; 77 | mach->clone_data (this); 78 | 79 | return mach; 80 | } 81 | 82 | bool StateMachine::is_unique_id(std::string const&state_id) const 83 | { 84 | return !manager_ || manager_->is_unique_id(scxml_id_, state_id); 85 | 86 | } 87 | 88 | std::vector StateMachine::getCurrentStateId() const 89 | { 90 | vector stateids; 91 | size_t lfsize = leaf_states_.size(); 92 | for (size_t i=0; i < lfsize; ++i) { 93 | stateids.push_back(leaf_states_[i]->state_id()); 94 | } 95 | return stateids; 96 | } 97 | 98 | std::vector StateMachine::getCurrentStateUId() const 99 | { 100 | vector stateids; 101 | size_t lfsize = leaf_states_.size(); 102 | for (size_t i=0; i < lfsize; ++i) { 103 | stateids.push_back(leaf_states_[i]->state_uid()); 104 | } 105 | return stateids; 106 | } 107 | 108 | State* StateMachine::getState(std::string const& state_uid) const 109 | { 110 | map::const_iterator it = states_map_.find(state_uid); 111 | if (it == states_map_.end()) return 0; 112 | return it->second; 113 | } 114 | 115 | void StateMachine::addState (State *state) 116 | { 117 | assert (state && "add state error!"); 118 | if (!state) return; 119 | string uid = state->state_uid(); 120 | states_map_[uid] = state; 121 | } 122 | 123 | void StateMachine::removeState (State *state) 124 | { 125 | assert (state && "remove state error!"); 126 | if (!state || state == this) return; 127 | if (states_map_[state->state_uid()] == state) states_map_.erase(state->state_uid()); 128 | } 129 | 130 | bool StateMachine::inState (std::string const &state_uid) const 131 | { 132 | map::const_iterator it = states_map_.find(state_uid); 133 | if (it == states_map_.end()) return false; 134 | return it->second->active(); 135 | } 136 | 137 | double StateMachine::elapsed_time_of_current_state() const 138 | { 139 | return getEnterState()->total_elapsed_time(); 140 | } 141 | 142 | void StateMachine::onFrameMove (float t) 143 | { 144 | if (!slots_connected_) return; 145 | State::onFrameMove (t); 146 | pumpTimedEvents(); 147 | while (!private_->queued_events_.empty()) { 148 | pumpQueuedEvents (); 149 | } 150 | } 151 | 152 | void StateMachine::pumpQueuedEvents () 153 | { 154 | if (private_->queued_events_.empty ()) { 155 | return; 156 | } 157 | 158 | std::vector events; 159 | events.swap(private_->queued_events_); 160 | for (size_t i=0; i < events.size (); ++i) { 161 | this->onEvent (events[i]); 162 | } 163 | } 164 | 165 | void StateMachine::enqueEvent(string const&e) 166 | { 167 | private_->queued_events_.push_back (e); 168 | manager_->addToActiveMach (this); 169 | } 170 | 171 | 172 | void StateMachine::onEvent(string const&e) 173 | { 174 | if (!this->slots_connected_) { 175 | assert (0 && " slots not connected"); 176 | return; 177 | } 178 | 179 | if (on_event_) { 180 | this->enqueEvent (e); 181 | return; 182 | } 183 | 184 | on_event_ = true; 185 | State::onEvent (e); 186 | on_event_ = false; 187 | } 188 | 189 | void StateMachine::prepareEngine () 190 | { 191 | assert (scxml_loaded_ && "no scxml loaded"); 192 | if (!scxml_loaded_) { 193 | assert (0 && "scxml not loaded!"); 194 | return; 195 | } 196 | this->prepare_slots (); 197 | this->connect_slots (); 198 | } 199 | 200 | void StateMachine::StartEngine () 201 | { 202 | assert (scxml_loaded_ && "no scxml loaded"); 203 | if (engine_started_) return; 204 | prepareEngine (); 205 | engine_started_ = true; 206 | this->enterState (); 207 | } 208 | 209 | void StateMachine::ReStartEngine () 210 | { 211 | assert (scxml_loaded_ && "no scxml loaded"); 212 | if (engine_started_) { 213 | ShutDownEngine(true); 214 | } 215 | StartEngine(); 216 | } 217 | 218 | bool StateMachine::engineStarted() const 219 | { 220 | return engine_started_; 221 | } 222 | 223 | 224 | void StateMachine::ShutDownEngine (bool do_exit_state) 225 | { 226 | if (do_exit_state) this->exitState(); 227 | engine_started_ = false; 228 | } 229 | 230 | bool StateMachine::engineReady () const 231 | { 232 | return scxml_loaded_; 233 | } 234 | 235 | bool StateMachine::isLeavingState () const 236 | { 237 | return this->getEnterState ()->isLeavingState (); 238 | } 239 | 240 | void StateMachine::prepare_slots () 241 | { 242 | if (slots_prepared_) return; 243 | 244 | slots_prepared_ = true; 245 | prepareActionCondSlots (); 246 | onPrepareActionCondSlots (); 247 | signal_prepare_slots_ (); 248 | } 249 | 250 | void StateMachine::connect_slots () 251 | { 252 | if (slots_connected_) { 253 | return; 254 | } 255 | slots_connected_ = true; 256 | 257 | connectCondSlots (); 258 | onConnectCondSlots (); 259 | signal_connect_cond_slots_ (); 260 | 261 | connectActionSlots (); 262 | onConnectActionSlots (); 263 | signal_connect_action_slots_ (); 264 | } 265 | 266 | void StateMachine::clear_slots () 267 | { 268 | slots_connected_ = false; 269 | 270 | if (action_slots_) { 271 | this->action_slots_->clear (); 272 | } 273 | 274 | if (cond_slots_) this->cond_slots_->clear (); 275 | 276 | if (frame_move_slots_) { 277 | this->frame_move_slots_->clear (); 278 | } 279 | 280 | this->transitions_.clear (); 281 | } 282 | 283 | void StateMachine::destroy_machine (bool do_exit_state) 284 | { 285 | if (slots_connected_) { 286 | if (do_exit_state) this->exitState (); 287 | scxml_loaded_ = false; 288 | } 289 | private_->queued_events_.clear (); 290 | states_map_.clear(); 291 | machine_clear_substates (); 292 | clear_slots (); 293 | reset_history (); 294 | 295 | // clean machine 296 | delete frame_move_slots_; 297 | delete cond_slots_; 298 | delete action_slots_; 299 | 300 | frame_move_slots_ = 0; 301 | cond_slots_ = 0; 302 | action_slots_ = 0; 303 | 304 | engine_started_ = false; 305 | } 306 | 307 | void StateMachine::PRIVATE::loadSCXMLString (std::string const&xmlstr) 308 | { 309 | if (mach_->scxml_loaded_) mach_->destroy_machine (); 310 | mach_->scxml_loaded_ = mach_->manager_->loadMachFromString (mach_, xmlstr); 311 | if (!mach_->scxml_loaded_) 312 | mach_->destroy_machine (); 313 | mach_->onLoadScxmlFailed (); 314 | assert ("load scxml string failed." && 0); 315 | } 316 | 317 | 318 | bool StateMachine::GetCondSlot (string const&name, boost::function &s) 319 | { 320 | if (!cond_slots_) return false; 321 | 322 | StateMachine::cond_slot_map::iterator it = this->cond_slots_->find (name); 323 | if (it != this->cond_slots_->end ()) { 324 | s = it->second; 325 | return true; 326 | } 327 | 328 | return false; 329 | } 330 | 331 | 332 | bool StateMachine::GetActionSlot (string const&name, boost::function &s) 333 | { 334 | if (!action_slots_) return false; 335 | 336 | StateMachine::action_slot_map::iterator it = this->action_slots_->find (name); 337 | if (it != this->action_slots_->end ()) { 338 | s = it->second; 339 | return true; 340 | } 341 | 342 | return false; 343 | } 344 | 345 | bool StateMachine::GetFrameMoveSlot (std::string const&name, boost::function &s) 346 | { 347 | if (!frame_move_slots_) return false; 348 | 349 | StateMachine::frame_move_map::iterator it = this->frame_move_slots_->find (name); 350 | if (it != this->frame_move_slots_->end ()) { 351 | s = it->second; 352 | return true; 353 | } 354 | 355 | return false; 356 | } 357 | 358 | void StateMachine::setCondSlot (std::string const&name, boost::function const &s) 359 | { 360 | if (!cond_slots_) { 361 | cond_slots_ = new cond_slot_map; 362 | } 363 | 364 | (*cond_slots_)[name] = s; 365 | } 366 | 367 | void StateMachine::setActionSlot (std::string const&name, boost::function const &s) 368 | { 369 | if (!action_slots_) { 370 | action_slots_ = new action_slot_map; 371 | } 372 | 373 | (*action_slots_)[name] = s; 374 | } 375 | 376 | void StateMachine::setFrameMoveSlot (std::string const&name, boost::function const &s) 377 | { 378 | if (!frame_move_slots_) { 379 | frame_move_slots_ = new frame_move_map; 380 | } 381 | 382 | (*frame_move_slots_)[name] = s; 383 | } 384 | 385 | TimedEventType * StateMachine::registerTimedEvent (float after_t, string const&event_e, bool cancelable) 386 | { 387 | TimedEventType * p = new TimedEventType(after_t + total_elapsed_time_, event_e, cancelable); 388 | list ::iterator it = std::upper_bound (private_->timed_events_.begin (), private_->timed_events_.end (), p, StateTimedEventTypeCmpP()); 389 | private_->timed_events_.insert (it, p); 390 | return p; 391 | } 392 | 393 | void StateMachine::clearTimedEvents () 394 | { 395 | list ::iterator it = private_->timed_events_.begin (); 396 | list ::iterator it_end = private_->timed_events_.end (); 397 | for (; it != it_end ; ++it) { 398 | (*it)->release (); 399 | } 400 | private_->timed_events_.clear (); 401 | } 402 | 403 | void StateMachine::pumpTimedEvents () 404 | { 405 | if (!private_->timed_events_.empty ()) { 406 | list ::iterator it = private_->timed_events_.begin (); 407 | for (; it != private_->timed_events_.end () ;) { 408 | if ((*it)->time_ <= this->total_elapsed_time_) { 409 | if (!(*it)->cancelable_ || !(*it)->unique_ref ()) { 410 | machine_->enqueEvent ((*it)->event_); 411 | } 412 | (*it)->release (); 413 | private_->timed_events_.erase (it++); 414 | } else { 415 | break; 416 | } 417 | } 418 | } 419 | } 420 | 421 | std::string const& StateMachine::state_id_of_history(const string& history_id) const 422 | { 423 | return manager_->history_id_resided_state(scxml_id_, history_id); 424 | } 425 | 426 | std::string const& StateMachine::history_type(std::string const& state_uid) const 427 | { 428 | return manager_->history_type(scxml_id_, state_uid); 429 | } 430 | 431 | const string& StateMachine::initial_state_of_state(std::string const& state_uid) const 432 | { 433 | return manager_->initial_state_of_state(scxml_id_, state_uid); 434 | } 435 | 436 | const string& StateMachine::onentry_action(std::string const& state_uid) const 437 | { 438 | return manager_->onentry_action(scxml_id_, state_uid); 439 | } 440 | 441 | const string& StateMachine::onexit_action(std::string const& state_uid) const 442 | { 443 | return manager_->onexit_action(scxml_id_, state_uid); 444 | } 445 | 446 | const string& StateMachine::frame_move_action(std::string const& state_uid) const 447 | { 448 | return manager_->frame_move_action(scxml_id_, state_uid); 449 | } 450 | 451 | std::vector< TransitionAttr* > StateMachine::transition_attr(std::string const& state_uid) const 452 | { 453 | return manager_->transition_attr(scxml_id_, state_uid); 454 | } 455 | 456 | size_t StateMachine::num_of_states() const 457 | { 458 | return this->states_map_.size(); 459 | } 460 | 461 | const vector< string > & StateMachine::get_all_states() const 462 | { 463 | return manager_->get_all_states (scxml_id_); 464 | } 465 | 466 | } 467 | 468 | -------------------------------------------------------------------------------- /scm/StateMachine.h: -------------------------------------------------------------------------------- 1 | #ifndef StateMachine_H 2 | #define StateMachine_H 3 | 4 | #include "State.h" 5 | #include "Parallel.h" 6 | #include "RefCountObject.h" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace scm { 14 | 15 | class StateMachineManager; 16 | 17 | struct TimedEventType: public RefCountObject 18 | { 19 | double time_; 20 | std::string event_; 21 | bool cancelable_; 22 | 23 | TimedEventType (double time, std::string const&str, bool cancelable) 24 | :time_(time), event_(str), cancelable_(cancelable) 25 | {} 26 | 27 | bool operator< (TimedEventType const&rhs) const 28 | { 29 | return time_ < rhs.time_; 30 | } 31 | }; 32 | 33 | 34 | /** StateMachine 35 | * 以 scxml 為基礎。請參考 https://www.w3.org/TR/scxml/ 36 | * Based on scxml, please refer to https://www.w3.org/TR/scxml/ 37 | */ 38 | class StateMachine : public State 39 | { 40 | typedef State super; 41 | 42 | protected: 43 | typedef std::map > frame_move_map; 44 | typedef std::map > cond_slot_map; 45 | typedef std::map > action_slot_map; // used for onentry, onexit, and ontransit, etc. 46 | 47 | std::string scxml_id_; 48 | 49 | StateMachineManager *manager_; 50 | 51 | bool slots_prepared_; 52 | bool slots_connected_; 53 | bool scxml_loaded_; 54 | bool on_event_; 55 | 56 | bool with_history_; 57 | bool allow_nop_entry_exit_slot_; 58 | bool do_exit_state_on_destroy_; 59 | 60 | bool engine_started_; 61 | 62 | frame_move_map *frame_move_slots_; 63 | cond_slot_map *cond_slots_; 64 | action_slot_map *action_slots_; 65 | 66 | std::string transition_source_state_; 67 | std::string transition_target_state_; 68 | 69 | std::map states_map_; 70 | 71 | State *current_enter_state_; 72 | std::vector leaf_states_; 73 | 74 | friend class State; 75 | friend class Parallel; 76 | friend class StateMachineManager; 77 | 78 | protected: 79 | void destroy_machine (bool do_exit_state=true); 80 | 81 | void prepare_slots (); 82 | void connect_slots (); 83 | void clear_slots (); 84 | 85 | virtual void onPrepareActionCondSlots () {} 86 | virtual void onConnectCondSlots () {} 87 | virtual void onConnectActionSlots () {} 88 | virtual void onLoadScxmlFailed () {} 89 | 90 | void onEvent(std::string const&e); 91 | 92 | virtual void onFrameMove (float t); 93 | 94 | StateMachine (StateMachineManager *manager); 95 | 96 | /** \brief after t seconds, enqueEvent event_e. If cancelable is true, you must retain and release later the returned object.*/ 97 | TimedEventType * registerTimedEvent(float after_t, std::string const&event_e, bool cancelable); 98 | 99 | public: 100 | 101 | virtual ~StateMachine (); 102 | 103 | std::string const & transition_source_state () const { return transition_source_state_; } 104 | std::string const & transition_target_state () const { return transition_target_state_; } 105 | 106 | bool engineStarted () const; 107 | 108 | StateMachineManager *manager () { return manager_;} 109 | 110 | bool is_unique_id (std::string const&state_id) const; 111 | 112 | std::string const & scxml_id () const { 113 | return scxml_id_; 114 | } 115 | 116 | State *getEnterState () const { 117 | return current_enter_state_; 118 | } 119 | 120 | const std::vector & getCurrentLeafStates() const { 121 | return leaf_states_; 122 | } 123 | 124 | std::vector getCurrentStateId() const; 125 | 126 | std::vector getCurrentStateUId() const; 127 | 128 | bool re_enter_state() const { 129 | return transition_source_state_ == transition_target_state_; 130 | } 131 | 132 | State *getState (std::string const& state_uid) const; 133 | void addState (State *state); 134 | void removeState (State *state); 135 | virtual bool inState (std::string const&state_uid) const; 136 | 137 | double elapsed_time_of_current_state() const; 138 | 139 | /** \brief 將 event e 加到 event queue 中等待處理. Add event_e to event queue.*/ 140 | void enqueEvent(std::string const&e); 141 | 142 | void prepareEngine (); 143 | 144 | void StartEngine (); 145 | 146 | void ReStartEngine (); 147 | 148 | /** \brief 停止 StateMachine, do_exit_state 指定是否呼叫 exitState()。 */ 149 | virtual void ShutDownEngine (bool do_exit_state); 150 | 151 | void set_do_exit_state_on_destroy (bool yes) { 152 | do_exit_state_on_destroy_ = yes; 153 | } 154 | 155 | /** \brief 是否scxml已經載入完成。 Whether scxml already loaded. */ 156 | bool engineReady () const; 157 | 158 | /** 是否正在離開某 state, 當有設定 leaving_delay時, 狀態會維持在離開中指定的時間。 159 | * Whether we are leaving this state? if leaving delay was set, machine will stay at this state for designated time. 160 | */ 161 | bool isLeavingState () const; 162 | 163 | /** 把所有外部 event 做一次處理。 164 | * Handle all queued events. 165 | */ 166 | void pumpQueuedEvents (); 167 | 168 | bool GetCondSlot (std::string const&name, boost::function &s); 169 | bool GetActionSlot (std::string const&name, boost::function &s); 170 | bool GetFrameMoveSlot (std::string const&name, boost::function &s); 171 | 172 | /** 建立 name 與 slot s 的對應,以供scxml 中的 cond 條件使用。 173 | * Mapping name and condition slot. Used in Transition conditions. 174 | */ 175 | void setCondSlot (std::string const&name, boost::function const &s); 176 | /** 建立 name 與 slot s 的對應,以供scxml 中各state的 onentry, onexit 使用。 177 | * Mapping name and action slot. Used in state's onentry, onexit, etc. 178 | */ 179 | void setActionSlot (std::string const&name, boost::function const &s); 180 | /** 建立 name 與 slot s 的對應,以供scxml 中各state的 frame_move 使用。 181 | * Mapping namd and frame_move slot. Used in state's frame_move. 182 | */ 183 | void setFrameMoveSlot (std::string const&name, boost::function const &s); 184 | 185 | StateMachine* clone (); 186 | 187 | inline bool allow_nop_entry_exit () const { 188 | return allow_nop_entry_exit_slot_; 189 | } 190 | void set_allow_nop_entry_exit (bool yes) { 191 | allow_nop_entry_exit_slot_ = yes; 192 | } 193 | 194 | void registerTimedEvent(float after_t, std::string const&event_e) { registerTimedEvent(after_t, event_e, false); } 195 | TimedEventType * registerTimedEvent_cancelable(float after_t, std::string const&event_e) { return registerTimedEvent(after_t, event_e, true); } 196 | void clearTimedEvents (); 197 | void pumpTimedEvents (); 198 | 199 | std::string const& state_id_of_history (std::string const&history_id) const; 200 | std::string const& history_type (std::string const& state_uid) const; 201 | std::string const& initial_state_of_state (std::string const& state_uid) const; 202 | std::string const& onentry_action (std::string const& state_uid) const; 203 | std::string const& onexit_action (std::string const& state_uid) const; 204 | std::string const& frame_move_action (std::string const& state_uid) const; 205 | std::vector transition_attr (std::string const& state_uid) const; 206 | size_t num_of_states () const; 207 | const std::vector & get_all_states () const; 208 | 209 | public: 210 | // signals 211 | bs2::signal signal_prepare_slots_; 212 | bs2::signal signal_connect_cond_slots_; 213 | bs2::signal signal_connect_action_slots_; 214 | 215 | 216 | private: 217 | struct PRIVATE; 218 | friend struct PRIVATE; 219 | PRIVATE *private_; 220 | }; 221 | 222 | #define REGISTER_STATE_SLOT(mach, state, onentry, onexit, obj) \ 223 | { \ 224 | boost::function slot = boost::bind (onentry, obj); \ 225 | mach->setActionSlot ("onentry_" state, slot); \ 226 | slot = boost::bind (onexit, obj); \ 227 | mach->setActionSlot ("onexit_" state, slot); \ 228 | } 229 | 230 | #define DECLARE_STATE_ENTRIES(state) \ 231 | void onentry_##state (); \ 232 | void onexit_##state (); 233 | 234 | #define REGISTER_ACTION_SLOT(mach, action, method, obj) \ 235 | mach->setActionSlot (action, boost::bind(method, obj)); 236 | 237 | #define REGISTER_FRAME_MOVE_SLOT(mach, state, method, obj) \ 238 | mach->setFrameMoveSlot (state, boost::bind(method, obj, _1)); 239 | 240 | #define REGISTER_COND_SLOT(mach, cond, method, obj) \ 241 | mach->setCondSlot (cond, boost::bind(method, obj)); 242 | 243 | 244 | } 245 | 246 | #endif 247 | -------------------------------------------------------------------------------- /scm/StateMachineManager.cpp: -------------------------------------------------------------------------------- 1 | #include "StateMachineManager.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | using boost::property_tree::ptree; 10 | using boost::property_tree::read_xml; 11 | using boost::property_tree::read_json; 12 | using namespace std; 13 | 14 | namespace scm { 15 | 16 | class FileCloser: Uncopyable 17 | { 18 | FILE *f_; 19 | public: 20 | FileCloser(FILE *f) 21 | :f_(f) 22 | {} 23 | 24 | ~FileCloser() 25 | { 26 | fclose(f_); 27 | } 28 | }; 29 | 30 | static StateMachineManager *static_instance_; 31 | 32 | template 33 | class array_guard { 34 | T *ptr_; 35 | public: 36 | array_guard (T *p) 37 | :ptr_(p) 38 | {} 39 | 40 | ~array_guard () 41 | { 42 | delete [] ptr_; 43 | } 44 | 45 | T *get () const 46 | { 47 | return ptr_; 48 | } 49 | }; 50 | 51 | size_t splitStringToVector (string const &valstr, vector &val, size_t max_s=0xffffffff, string const&separators=", \t") 52 | { 53 | if (valstr.empty ()) { 54 | return false; 55 | } 56 | 57 | size_t len = valstr.length (); 58 | string _str = valstr; 59 | // append ',' for later parsing logic 60 | if (separators.find (_str[len-1]) == string::npos) 61 | { 62 | _str.push_back(separators[0]); 63 | ++len; 64 | } 65 | 66 | size_t begin=0, end; 67 | size_t count=0; 68 | bool single_quote_active = false; 69 | bool double_quote_active = false; 70 | string tmp; 71 | for (size_t i=0; i < len; ++i) 72 | { 73 | bool reach = false; 74 | if (single_quote_active) { 75 | if (_str[i] == '\'') reach = true; 76 | } else if (double_quote_active) { 77 | if (_str[i] == '\"') reach = true; 78 | } else if (separators.find (_str[i]) != string::npos) { 79 | reach = true; 80 | } 81 | 82 | if (reach) { 83 | end = i; 84 | if (count >= max_s) 85 | { 86 | return false; 87 | } 88 | 89 | if (end-begin) { 90 | val.push_back (_str.substr (begin, end-begin)); 91 | count++; 92 | } 93 | if (single_quote_active || double_quote_active) { 94 | ++i; 95 | ++end; 96 | single_quote_active = double_quote_active = false; 97 | } 98 | 99 | for (size_t j=end+1; j < len; ++j, ++i) 100 | { 101 | if (separators.find (_str[j]) == string::npos) 102 | { 103 | if (_str[j]=='\'') { 104 | single_quote_active = true; 105 | ++j; 106 | ++i; 107 | } else if (_str[j]=='\"') { 108 | double_quote_active = true; 109 | ++j; 110 | ++i; 111 | } 112 | begin = j; 113 | break; 114 | } 115 | } 116 | } 117 | } 118 | 119 | return count; 120 | } 121 | //---------------------------------------------- 122 | namespace { 123 | struct ParseStruct { 124 | State *current_state_; 125 | StateMachine *machine_; 126 | string scxml_id_; 127 | 128 | ParseStruct () 129 | :current_state_(0), machine_(0) 130 | {} 131 | }; 132 | } 133 | //---------------------------------------------- 134 | 135 | struct StateMachineManager::PRIVATE 136 | { 137 | StateMachineManager * manager_; 138 | std::list active_machs_; 139 | 140 | // ids are unique 141 | map mach_map_; 142 | map > onentry_action_map_; 143 | map > onexit_action_map_; 144 | map > frame_move_action_map_; 145 | map > initial_state_map_; 146 | map > history_type_map_; 147 | map > history_id_reside_state_; 148 | map > non_unique_ids_; 149 | map > state_uids_; 150 | map > > transition_attr_map_; 151 | 152 | map scxml_map_; 153 | 154 | PRIVATE(StateMachineManager *manager) 155 | : manager_(manager) 156 | { 157 | } 158 | 159 | ~PRIVATE() 160 | { 161 | clearMachMap(); 162 | } 163 | 164 | StateMachine *getMach (string const&scxml_id); 165 | void clearMachMap (); 166 | 167 | static void get_item_attrs_in_ptree(ptree& pt, map &attrs_map); 168 | static void parse_element(ParseStruct &data, ptree &pt, int level); 169 | static bool parse_scm_tree (ParseStruct &data, string const&scm_str); 170 | 171 | static void finish_scxml (ParseStruct &data, map &attributes); 172 | static void handle_state_item (ParseStruct &data, map &attributes); 173 | static void handle_final_item (ParseStruct &data, map &attributes); 174 | static void handle_transition_item (ParseStruct &data, map &attributes); 175 | static void handle_history_item (ParseStruct &data, map &attributes); 176 | }; 177 | 178 | void StateMachineManager::PRIVATE::get_item_attrs_in_ptree(ptree& pt, map &attrs_map) 179 | { 180 | ptree::iterator end = pt.end(); 181 | for (ptree::iterator pt_it = pt.begin(); pt_it != end; ++pt_it) { 182 | if (pt_it->first == "") { 183 | return get_item_attrs_in_ptree(pt_it->second, attrs_map); 184 | } else { 185 | if (pt_it->second.empty()) { 186 | attrs_map[pt_it->first] = pt_it->second.data(); 187 | } 188 | } 189 | } 190 | } 191 | namespace { 192 | void validate_state_id (string const&stateid) 193 | { 194 | if (!stateid.empty() && stateid[0] == '_') { 195 | assert (0 && "state id can't start with '_', which is reserved for internal use."); 196 | throw std::runtime_error("state id can't start with '_', which is reserved for internal use."); 197 | } 198 | } 199 | } 200 | 201 | void StateMachineManager::PRIVATE::parse_element(ParseStruct &data, ptree &pt, int level) 202 | { 203 | StateMachineManager *manager = data.machine_->manager(); 204 | const string &scxml_id = data.scxml_id_; 205 | 206 | ptree::iterator end = pt.end(); 207 | for (ptree::iterator pt_it = pt.begin(); pt_it != end; ++pt_it) { 208 | if (pt_it->first == "") { 209 | parse_element(data, pt_it->second, level); 210 | } else { 211 | //for (int i=0; i < level; ++i) { 212 | // cout << "\t"; 213 | //} 214 | 215 | if (pt_it->second.empty()) { 216 | if (pt_it->first == "non-unique") { 217 | vector non_unique_ids; 218 | splitStringToVector(pt_it->second.data(), non_unique_ids); 219 | data.machine_->manager()->private_->non_unique_ids_[scxml_id].insert(non_unique_ids.begin(), non_unique_ids.end()); 220 | } else { 221 | // 222 | } 223 | // cout << pt_it->first << " = " << pt_it->second.data() << endl; 224 | } else { 225 | string tag = pt_it->first.data(); 226 | // cout << tag << ": \n"; 227 | map attrs_map; 228 | get_item_attrs_in_ptree (pt_it->second, attrs_map); 229 | 230 | if (tag == "scxml") { 231 | manager->private_->state_uids_[scxml_id].reserve(16); 232 | manager->private_->state_uids_[scxml_id].push_back(scxml_id); 233 | } else if (tag == "state") { 234 | string stateid = attrs_map["id"]; 235 | // cout << "id -> " << stateid << endl; 236 | validate_state_id (stateid); 237 | State *state = new State(stateid, data.current_state_, data.machine_); 238 | data.current_state_->substates_.push_back (state); 239 | data.current_state_ = state; 240 | manager->private_->state_uids_[scxml_id].push_back(state->state_uid()); 241 | handle_state_item(data, attrs_map); 242 | } else if (tag == "parallel") { 243 | string stateid = attrs_map["id"]; 244 | // cout << "id -> " << stateid << endl; 245 | validate_state_id (stateid); 246 | Parallel *state = new Parallel(stateid, data.current_state_, data.machine_); 247 | data.current_state_->substates_.push_back (state); 248 | data.current_state_ = state; 249 | manager->private_->state_uids_[scxml_id].push_back(state->state_uid()); 250 | handle_state_item(data, attrs_map); 251 | } else if (tag == "final") { 252 | string stateid = attrs_map["id"]; 253 | // cout << "id -> " << stateid << endl; 254 | validate_state_id (stateid); 255 | State *state = new State(stateid, data.current_state_, data.machine_); 256 | data.current_state_->substates_.push_back (state); 257 | data.current_state_ = state; 258 | manager->private_->state_uids_[scxml_id].push_back(state->state_uid()); 259 | handle_final_item(data, attrs_map); 260 | } else if (tag == "history") { 261 | handle_history_item(data, attrs_map); 262 | } else if (tag == "transition") { 263 | handle_transition_item(data, attrs_map); 264 | } 265 | 266 | parse_element(data, pt_it->second, level+1); 267 | 268 | if (tag == "scxml") { 269 | finish_scxml (data, attrs_map); 270 | data.current_state_ = data.current_state_->parent_; 271 | } else if (tag == "state") { 272 | data.current_state_ = data.current_state_->parent_; 273 | } else if (tag == "parallel") { 274 | data.current_state_ = data.current_state_->parent_; 275 | } else if (tag == "final") { 276 | data.current_state_ = data.current_state_->parent_; 277 | } 278 | } 279 | } 280 | } 281 | } 282 | 283 | void StateMachineManager::PRIVATE::finish_scxml(ParseStruct& data, map &attributes) 284 | { 285 | StateMachineManager *manager = data.machine_->manager(); 286 | const string &scxml_id = data.scxml_id_; 287 | 288 | map::iterator it = attributes.find("initial"); 289 | if (it != attributes.end()) { 290 | manager->private_->initial_state_map_[scxml_id][data.current_state_->state_uid()] = it->second; 291 | } 292 | 293 | // check transition settings 294 | map > &transition_map = manager->private_->transition_attr_map_[scxml_id]; 295 | map > ::iterator tran_attr_it = transition_map.begin (); 296 | for (; tran_attr_it != transition_map.end (); ++tran_attr_it) { 297 | string const &state_uid = tran_attr_it->first; 298 | State *st = data.machine_->getState(state_uid); 299 | 300 | for (size_t i=0; i < tran_attr_it->second.size (); ++i) { 301 | string &target_str = tran_attr_it->second[i]->transition_target_; 302 | if (target_str.find(',') == string::npos && data.machine_->is_unique_id(target_str)) continue; 303 | // support multiple targets 304 | vector targets; 305 | vector tstates; 306 | splitStringToVector(target_str, targets); 307 | for (size_t i=0; i < targets.size(); ++i) { 308 | if (!data.machine_->is_unique_id(targets[i])) { 309 | State *s = st->findState(targets[i]); 310 | assert (s && "can't find transition target, not state id?"); 311 | targets[i] = s->state_uid(); 312 | } 313 | tstates.push_back(data.machine_->getState(targets[i])); 314 | } 315 | target_str = targets[0]; 316 | for (size_t i=1; i < targets.size(); ++i) { 317 | target_str += ("," + targets[i]); 318 | } 319 | 320 | // check multiple target have the same ancestor of parallel 321 | for (size_t i=1; i < tstates.size(); ++i) { 322 | State *lca = tstates[0]->findLCA (tstates[i]); 323 | assert (typeid(*lca) == typeid(Parallel) && "multiple targets but can't find common ancestor."); 324 | } 325 | 326 | } 327 | } 328 | } 329 | 330 | void StateMachineManager::PRIVATE::handle_state_item(ParseStruct& data, map &attributes) 331 | { 332 | StateMachineManager *manager = data.machine_->manager(); 333 | const string &scxml_id = data.scxml_id_; 334 | 335 | string onentry; 336 | string onexit; 337 | string framemove; 338 | string history_type; 339 | float leaving_delay = 0; 340 | map::iterator attr_it_end = attributes.end(); 341 | map::iterator attr_it = attributes.begin(); 342 | for (; attr_it != attr_it_end; ++attr_it) { 343 | if (attr_it->first == "initial") { 344 | manager->private_->initial_state_map_[scxml_id][data.current_state_->state_uid()] = attr_it->second; 345 | } else if (attr_it->first == "history") { 346 | history_type = attr_it->second; 347 | } else if (attr_it->first == "onentry") { 348 | onentry = attr_it->second; 349 | } else if (attr_it->first == "onexit") { 350 | onexit = attr_it->second; 351 | } else if (attr_it->first == "frame_move") { 352 | framemove = attr_it->second; 353 | } else if (attr_it->first == "leaving_delay") { 354 | leaving_delay = strtod (attr_it->second.c_str(), NULL); 355 | } 356 | } 357 | 358 | string state_uid = data.current_state_->state_uid(); 359 | 360 | data.current_state_->setLeavingDelay (leaving_delay); 361 | 362 | if (onentry.empty ()) onentry = "onentry_" + state_uid; 363 | 364 | if (onexit.empty ()) onexit = "onexit_" + state_uid; 365 | 366 | if (framemove.empty ()) framemove = state_uid; 367 | 368 | if (manager->private_->history_type_map_[scxml_id][data.current_state_->parent_->state_uid()] == "deep") { 369 | manager->private_->history_type_map_[scxml_id][state_uid] = "deep"; 370 | } else { 371 | manager->private_->history_type_map_[scxml_id][state_uid] = history_type; 372 | } 373 | 374 | if (!manager->private_->history_type_map_[scxml_id][state_uid].empty ()) { 375 | data.machine_->with_history_ = true; 376 | } 377 | 378 | manager->private_->onentry_action_map_[scxml_id][state_uid] = onentry; 379 | manager->private_->onexit_action_map_[scxml_id][state_uid] = onexit; 380 | manager->private_->frame_move_action_map_[scxml_id][state_uid] = framemove; 381 | 382 | } 383 | 384 | void StateMachineManager::PRIVATE::handle_final_item(ParseStruct& data, map &attributes) 385 | { 386 | StateMachineManager *manager = data.machine_->manager(); 387 | const string &scxml_id = data.scxml_id_; 388 | 389 | string onentry; 390 | string framemove; 391 | map::iterator attr_it_end = attributes.end(); 392 | map::iterator attr_it = attributes.begin(); 393 | for (; attr_it != attr_it_end; ++attr_it) { 394 | if (attr_it->first == "onentry") { 395 | onentry = attr_it->second; 396 | } else if (attr_it->first == "frame_move") { 397 | framemove = attr_it->second; 398 | } 399 | } 400 | 401 | string state_uid = data.current_state_->state_uid(); 402 | 403 | if (onentry.empty ()) onentry = "onentry_" + state_uid; 404 | if (framemove.empty ()) framemove = state_uid; 405 | 406 | manager->private_->onentry_action_map_[scxml_id][state_uid] = onentry; 407 | manager->private_->frame_move_action_map_[scxml_id][state_uid] = framemove; 408 | data.current_state_->is_a_final_ = true; 409 | 410 | } 411 | 412 | void StateMachineManager::PRIVATE::handle_transition_item(ParseStruct& data, map &attributes) 413 | { 414 | StateMachineManager *manager = data.machine_->manager(); 415 | 416 | TransitionAttr * tran = new TransitionAttr ("",""); 417 | 418 | map::iterator attr_it_end = attributes.end(); 419 | map::iterator attr_it = attributes.begin(); 420 | for (; attr_it != attr_it_end; ++attr_it) { 421 | if (attr_it->first == "event") { 422 | tran->event_ = attr_it->second; 423 | } else if (attr_it->first == "cond") { 424 | tran->cond_ = attr_it->second; 425 | } else if (attr_it->first == "ontransit") { 426 | tran->ontransit_ = attr_it->second; 427 | } else if (attr_it->first == "target") { 428 | tran->transition_target_ = attr_it->second; 429 | } else if (attr_it->first == "random_target") { 430 | vector values; 431 | splitStringToVector (attr_it->second.c_str(), values); 432 | tran->random_target_ = values; 433 | } 434 | } 435 | 436 | if (!tran->cond_.empty () && tran->cond_[0] == '!') { 437 | tran->cond_ = tran->cond_.substr (1); 438 | tran->not_ = true; 439 | } 440 | 441 | manager->private_->transition_attr_map_[data.scxml_id_][data.current_state_->state_uid()].push_back (tran); 442 | 443 | } 444 | 445 | void StateMachineManager::PRIVATE::handle_history_item(ParseStruct& data, map &attributes) 446 | { 447 | StateMachineManager *manager = data.machine_->manager(); 448 | 449 | const string &state_uid = data.current_state_->state_uid(); 450 | 451 | map::iterator attr_it_end = attributes.end(); 452 | map::iterator attr_it = attributes.begin(); 453 | for (; attr_it != attr_it_end; ++attr_it) { 454 | if (attr_it->first == "type") { 455 | manager->private_->history_type_map_[data.scxml_id_][state_uid] = attr_it->second; 456 | } else if (attr_it->first == "id") { 457 | manager->private_->history_id_reside_state_[data.scxml_id_][attr_it->second] = state_uid; 458 | } 459 | } 460 | 461 | } 462 | 463 | 464 | bool StateMachineManager::PRIVATE::parse_scm_tree (ParseStruct &data, string const&scm_str) 465 | { 466 | 467 | ptree pt; 468 | 469 | int idx=0; 470 | char first_char = scm_str[idx++]; 471 | while (isspace(first_char)) { 472 | first_char = scm_str[idx++]; 473 | } 474 | 475 | if (first_char == '<') { 476 | // xml 477 | try { 478 | istringstream stream(scm_str); 479 | read_xml(stream, pt); 480 | parse_element(data, pt, 0); 481 | } catch (exception &e) { 482 | // read xml failed 483 | cerr << "read scm scxml failed: " << e.what() << endl; 484 | return false; 485 | } 486 | } else { 487 | // json 488 | string scm_json = scm_str; 489 | for (size_t i=0; i < scm_json.length(); ++i) { 490 | if (scm_json[i] == '\'') { 491 | scm_json[i] = '"'; 492 | } 493 | } 494 | try { 495 | istringstream stream(scm_json); 496 | read_json(stream, pt); 497 | parse_element(data, pt, 0); 498 | } catch (exception &e) { 499 | // read json failed 500 | cerr << "read scm json failed: " << e.what() << endl; 501 | return false; 502 | } 503 | 504 | } 505 | 506 | return true; 507 | } 508 | 509 | 510 | StateMachineManager::StateMachineManager() 511 | { 512 | private_ = new PRIVATE(this); 513 | } 514 | 515 | StateMachineManager::~StateMachineManager() 516 | { 517 | delete private_; 518 | } 519 | 520 | 521 | StateMachineManager* StateMachineManager::instance() 522 | { 523 | if (!static_instance_) { 524 | static_instance_ = new StateMachineManager; 525 | } 526 | return static_instance_; 527 | } 528 | 529 | void StateMachineManager::release_instance() 530 | { 531 | delete static_instance_; 532 | static_instance_ = 0; 533 | } 534 | 535 | 536 | StateMachine *StateMachineManager::getMach (string const&scxml_id) 537 | { 538 | return private_->getMach(scxml_id); 539 | } 540 | 541 | void StateMachineManager::addToActiveMach(StateMachine* mach) 542 | { 543 | assert (mach); 544 | if (!mach) return; 545 | mach->retain(); 546 | private_->active_machs_.push_back(mach); 547 | } 548 | 549 | void StateMachineManager::pumpMachEvents() 550 | { 551 | while (!private_->active_machs_.empty ()) { 552 | std::list machs; 553 | machs.swap (private_->active_machs_); 554 | std::list::iterator it = machs.begin (); 555 | std::list::iterator it_end = machs.end (); 556 | for (; it != it_end; ++it) { 557 | (*it)->pumpQueuedEvents (); 558 | (*it)->release(); 559 | } 560 | } 561 | 562 | } 563 | 564 | void StateMachineManager::set_scxml(const string& scxml_id, const string& scxml_str) 565 | { 566 | private_->scxml_map_[scxml_id] = scxml_str; 567 | } 568 | 569 | void StateMachineManager::set_scxml_file(const string& scxml_id, const string& scxml_filepath) 570 | { 571 | private_->scxml_map_[scxml_id] = "file:" + scxml_filepath; 572 | } 573 | 574 | void StateMachineManager::prepare_machs() 575 | { 576 | map::iterator it = private_->scxml_map_.begin(); 577 | for (; it != private_->scxml_map_.end(); ++it) { 578 | string const&scxml_id = it->first; 579 | map::iterator itm = private_->mach_map_.find (scxml_id); 580 | StateMachine *mach = 0; 581 | if (itm == private_->mach_map_.end ()) { 582 | mach = new StateMachine (this); 583 | mach->scxml_id_ = scxml_id; 584 | private_->mach_map_[scxml_id] = mach; 585 | } else { 586 | mach = itm->second; 587 | } 588 | if (!mach->scxml_loaded_) { 589 | if (it->second.substr(0, 5) == "file:") { 590 | mach->scxml_loaded_ = this->loadMachFromFile(mach, it->second.substr(5)); 591 | } else { 592 | mach->scxml_loaded_ = this->loadMachFromString(mach, it->second); 593 | } 594 | // "prepare mach '%s' %s", mach->scxml_id_.c_str(), mach->scxml_loaded_ ? "done" : "fail"); 595 | } else { 596 | // "mach '%s' already prepared", mach->scxml_id_.c_str()); 597 | } 598 | } 599 | } 600 | 601 | 602 | bool StateMachineManager::loadMachFromFile(StateMachine* mach, const string& scxml_file) 603 | { 604 | FILE *zfile = fopen(scxml_file.c_str (), "rb" ); 605 | 606 | if( zfile == NULL ) 607 | { 608 | // "Error: can't open file " + scxml_file); 609 | return false; 610 | } 611 | 612 | FileCloser guard(zfile); 613 | 614 | fseek(zfile, 0, SEEK_END); 615 | size_t file_length = ftell (zfile); 616 | rewind (zfile); 617 | string scxml_str; 618 | scxml_str.resize(file_length); 619 | size_t readlen = fread(&scxml_str[0], 1, file_length, zfile); 620 | if (readlen < file_length) { 621 | return false; 622 | } 623 | return loadMachFromString(mach, scxml_str); 624 | } 625 | 626 | bool StateMachineManager::loadMachFromString(StateMachine* mach, const string& scm_str) 627 | { 628 | ParseStruct parse; 629 | parse.scxml_id_ = mach->scxml_id (); 630 | parse.machine_ = mach; 631 | parse.current_state_ = mach; 632 | private_->transition_attr_map_[parse.scxml_id_].clear(); 633 | 634 | return private_->parse_scm_tree(parse, scm_str); 635 | } 636 | 637 | 638 | StateMachine* StateMachineManager::PRIVATE::getMach(const string& scxml_id) 639 | { 640 | map::iterator it = mach_map_.find (scxml_id); 641 | if (it != mach_map_.end ()) { 642 | return it->second->clone (); 643 | } else { 644 | StateMachine *mach = new StateMachine (manager_); 645 | mach_map_[scxml_id] = mach; 646 | mach->scxml_id_ = scxml_id; 647 | map::iterator it = scxml_map_.find(scxml_id); 648 | if (it != scxml_map_.end()) { 649 | if (it->second.substr(0, 5) == "file:") { 650 | mach->scxml_loaded_ = manager_->loadMachFromFile(mach, it->second.substr(5)); 651 | } else { 652 | mach->scxml_loaded_ = manager_->loadMachFromString(mach, it->second); 653 | } 654 | } 655 | return mach->clone (); 656 | } 657 | } 658 | 659 | void StateMachineManager::PRIVATE::clearMachMap () 660 | { 661 | for (map ::iterator it=mach_map_.begin (); it != mach_map_.end (); ++it) { 662 | it->second->release (); 663 | } 664 | mach_map_.clear (); 665 | onentry_action_map_.clear (); 666 | onexit_action_map_.clear (); 667 | frame_move_action_map_.clear (); 668 | initial_state_map_.clear (); 669 | history_type_map_.clear (); 670 | history_id_reside_state_.clear(); 671 | 672 | map > >::iterator it = transition_attr_map_.begin (); 673 | for (; it != transition_attr_map_.end (); ++it) { 674 | map >::iterator it2 = it->second.begin (); 675 | for (; it2 != it->second.end (); ++it2) { 676 | for (size_t i=0; i < it2->second.size (); ++i) { 677 | it2->second[i]->release (); 678 | } 679 | } 680 | } 681 | transition_attr_map_.clear (); 682 | } 683 | 684 | string const& StateMachineManager::history_id_resided_state(const string& scxml_id, const string& history_id) const 685 | { 686 | return private_->history_id_reside_state_[scxml_id][history_id]; 687 | } 688 | 689 | 690 | string const& StateMachineManager::history_type(const string& scxml_id, string const& state_uid) const 691 | { 692 | return private_->history_type_map_[scxml_id][state_uid]; 693 | } 694 | 695 | const string& StateMachineManager::initial_state_of_state(const string& scxml_id, string const& state_uid) const 696 | { 697 | return private_->initial_state_map_[scxml_id][state_uid]; 698 | } 699 | 700 | const string& StateMachineManager::onentry_action(const string& scxml_id, string const& state_uid) const 701 | { 702 | return private_->onentry_action_map_[scxml_id][state_uid]; 703 | } 704 | 705 | const string& StateMachineManager::onexit_action(const string& scxml_id, string const& state_uid) const 706 | { 707 | return private_->onexit_action_map_[scxml_id][state_uid]; 708 | } 709 | 710 | const string& StateMachineManager::frame_move_action(const string& scxml_id, string const& state_uid) const 711 | { 712 | return private_->frame_move_action_map_[scxml_id][state_uid]; 713 | } 714 | 715 | vector< TransitionAttr* > StateMachineManager::transition_attr(const string& scxml_id, string const& state_uid) const 716 | { 717 | return private_->transition_attr_map_[scxml_id][state_uid]; 718 | } 719 | 720 | size_t StateMachineManager::num_of_states(const string& scxml_id) const 721 | { 722 | return private_->state_uids_[scxml_id].size(); 723 | } 724 | 725 | bool StateMachineManager::is_unique_id(const string& scxml_id, const string& state_uid) const 726 | { 727 | return private_->non_unique_ids_[scxml_id].count(state_uid) == 0; 728 | } 729 | 730 | const vector< string > & StateMachineManager::get_all_states(const string& scxml_id) const 731 | { 732 | return private_->state_uids_[scxml_id]; 733 | } 734 | 735 | 736 | } 737 | -------------------------------------------------------------------------------- /scm/StateMachineManager.h: -------------------------------------------------------------------------------- 1 | #ifndef StateMachineManager_H 2 | #define StateMachineManager_H 3 | 4 | #include "StateMachine.h" 5 | #include "uncopyable.h" 6 | 7 | namespace scm { 8 | 9 | class StateMachineManager: public Uncopyable 10 | { 11 | public: 12 | static StateMachineManager *instance (); 13 | static void release_instance (); 14 | 15 | StateMachineManager (); 16 | ~StateMachineManager(); 17 | 18 | StateMachine *getMach (std::string const&scxml_id); 19 | 20 | void set_scxml (std::string const&scxml_id, std::string const&scxml_str); 21 | void set_scxml_file (std::string const&scxml_id, std::string const&scxml_filepath); 22 | void prepare_machs (); 23 | 24 | bool loadMachFromFile (StateMachine *mach, std::string const&scxml_file); 25 | bool loadMachFromString (StateMachine *mach, std::string const&scxml_str); 26 | 27 | std::string const& history_id_resided_state (std::string const&scxml_id, std::string const&history_id) const; 28 | std::string const& history_type (std::string const&scxml_id, std::string const& state_uid) const; 29 | std::string const& initial_state_of_state (std::string const&scxml_id, std::string const& state_uid) const; 30 | std::string const& onentry_action (std::string const&scxml_id, std::string const& state_uid) const; 31 | std::string const& onexit_action (std::string const&scxml_id, std::string const& state_uid) const; 32 | std::string const& frame_move_action (std::string const&scxml_id, std::string const& state_uid) const; 33 | std::vector transition_attr (std::string const&scxml_id, std::string const& state_uid) const; 34 | size_t num_of_states (const std::string& scxml_id) const; 35 | bool is_unique_id (const std::string& scxml_id, std::string const&state_uid) const; 36 | const std::vector & get_all_states (const std::string& scxml_id) const; 37 | 38 | void addToActiveMach(StateMachine* mach); 39 | void pumpMachEvents (); 40 | 41 | private: 42 | struct PRIVATE; 43 | friend struct PRIVATE; 44 | PRIVATE *private_; 45 | }; 46 | 47 | } 48 | 49 | #endif 50 | -------------------------------------------------------------------------------- /scm/tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | include_directories(../..) 3 | 4 | add_executable (scm_tutorial scm_tutorial.cpp) 5 | target_link_libraries (scm_tutorial scm) 6 | install (TARGETS scm_tutorial DESTINATION bin) 7 | 8 | add_executable (test_state_machine test-StateMachine.cpp) 9 | target_link_libraries (test_state_machine scm) 10 | install (TARGETS test_state_machine DESTINATION bin) 11 | 12 | add_executable (test_candy_machine test-CandyMachine.cpp) 13 | target_link_libraries (test_candy_machine scm) 14 | install (TARGETS test_candy_machine DESTINATION bin) 15 | 16 | add_executable (test_history_machine test-HistoryMachine.cpp) 17 | target_link_libraries (test_history_machine scm) 18 | install (TARGETS test_history_machine DESTINATION bin) 19 | -------------------------------------------------------------------------------- /scm/tests/scm_tutorial.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | using namespace std; 7 | using namespace scm; 8 | 9 | std::string client_scxml = "\ 10 | \ 11 | \ 12 | \ 13 | \ 14 | \ 15 | \ 16 | \ 17 | \ 18 | \ 19 | \ 20 | \ 21 | ' \ 22 | \ 23 | "; 24 | 25 | class Life: public Uncopyable 26 | { 27 | StateMachine *mach_; 28 | 29 | public: 30 | Life() 31 | { 32 | mach_ = StateMachineManager::instance()->getMach("the life"); 33 | mach_->retain(); 34 | mach_->set_do_exit_state_on_destroy(true); 35 | REGISTER_STATE_SLOT (mach_, "appear", &Life::onentry_appear, &Life::onexit_appear, this); 36 | REGISTER_STATE_SLOT (mach_, "live", &Life::onentry_live, &Life::onexit_live, this); 37 | REGISTER_STATE_SLOT (mach_, "eat", &Life::onentry_eat, &Life::onexit_eat, this); 38 | REGISTER_STATE_SLOT (mach_, "move", &Life::onentry_move, &Life::onexit_move, this); 39 | REGISTER_STATE_SLOT (mach_, "dead", &Life::onentry_dead, &Life::onexit_dead, this); 40 | REGISTER_ACTION_SLOT(mach_, "say_hello", &Life::say_hello, this); 41 | mach_->StartEngine(); 42 | } 43 | 44 | ~Life () 45 | { 46 | mach_->release(); 47 | } 48 | 49 | void onentry_appear () 50 | { 51 | cout << "come to exist" << endl; 52 | } 53 | 54 | void onexit_appear() 55 | { 56 | cout << "we are going to..." << endl; 57 | } 58 | 59 | void onentry_live () 60 | { 61 | cout << "start living" << endl; 62 | } 63 | 64 | void onexit_live () 65 | { 66 | cout << "no longer live" << endl; 67 | } 68 | 69 | void onentry_eat () 70 | { 71 | cout << "start eating" << endl; 72 | } 73 | 74 | void onexit_eat () 75 | { 76 | cout << "stop eating" << endl; 77 | } 78 | 79 | void onentry_move () 80 | { 81 | cout << "start moving" << endl; 82 | } 83 | 84 | void onexit_move () 85 | { 86 | cout << "stop moving" << endl; 87 | } 88 | 89 | void onentry_dead () 90 | { 91 | cout << "end" << endl; 92 | } 93 | 94 | void onexit_dead () 95 | { 96 | assert (0 && "should not exit final state"); 97 | cout << "no, this won't get called." << endl; 98 | } 99 | 100 | void say_hello () 101 | { 102 | cout << "\n*** Hello, World! ***\n" << endl; 103 | } 104 | 105 | void test () 106 | { 107 | mach_->enqueEvent("born"); 108 | mach_->frame_move(0); // state change to 'live' 109 | mach_->enqueEvent("hp_zero"); 110 | mach_->frame_move(0); // state change to 'dead' 111 | } 112 | }; 113 | 114 | int main(int argc, char* argv[]) 115 | { 116 | AutoReleasePool apool; 117 | StateMachineManager::instance()->set_scxml("the life", client_scxml); 118 | { 119 | Life life; 120 | life.test (); 121 | } 122 | StateMachineManager::instance()->pumpMachEvents(); 123 | StateMachineManager::instance()->release_instance(); 124 | AutoReleasePool::pumpPools(); 125 | return 0; 126 | } 127 | -------------------------------------------------------------------------------- /scm/tests/test-CandyMachine.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace std; 5 | using namespace scm; 6 | 7 | std::string cm_scxml = "\ 8 | \ 9 | \ 10 | \ 11 | \ 12 | \ 13 | \ 14 | \ 15 | \ 16 | \ 17 | \ 18 | \ 19 | \ 20 | \ 21 | \ 22 | \ 23 | \ 24 | \ 25 | \ 26 | "; 27 | 28 | std::string cm_json = "{\ 29 | 'scxml' : { \ 30 | 'state' : {\ 31 | 'id': 'idle',\ 32 | 'transition' : { 'event':'empty', 'target': 'disabled' },\ 33 | 'transition' : { 'event':'coin', 'target': 'active' }\ 34 | },\ 35 | 'state' : {\ 36 | 'id': 'active',\ 37 | 'transition' : { 'event':'release-candy', 'ontransit':'releaseCandy', 'target': 'releasing'},\ 38 | 'transition' : { 'event':'withdraw-coin', 'ontransit':'withdrawCoins', 'target': 'idle'}\ 39 | },\ 40 | 'state' : {\ 41 | 'id': 'releasing',\ 42 | 'transition' : { 'event':'candy-released', 'cond':'condNoCandy', 'target': 'disabled' },\ 43 | 'transition' : { 'event':'candy-released', 'target': 'idle' }\ 44 | },\ 45 | 'state' : {\ 46 | 'id': 'disabled',\ 47 | 'transition' : { 'event':'add-candy', 'cond':'condNoCredit', 'target': 'idle' },\ 48 | 'transition' : { 'event':'add-candy', 'target': 'active' }\ 49 | }\ 50 | }\ 51 | }"; 52 | 53 | class TheCandyMachine : public Uncopyable 54 | { 55 | StateMachine *mach_; 56 | int credit_; // 57 | int num_of_candy_stored_; 58 | 59 | public: 60 | TheCandyMachine() 61 | : credit_(0) 62 | , num_of_candy_stored_(0) 63 | { 64 | mach_ = StateMachineManager::instance()->getMach("cm_scxml"); 65 | REGISTER_STATE_SLOT (mach_, "idle", &TheCandyMachine::onentry_idle, &TheCandyMachine::onexit_idle, this); 66 | REGISTER_STATE_SLOT (mach_, "active", &TheCandyMachine::onentry_active, &TheCandyMachine::onexit_active, this); 67 | REGISTER_STATE_SLOT (mach_, "releasing", &TheCandyMachine::onentry_releasing, &TheCandyMachine::onexit_releasing, this); 68 | REGISTER_STATE_SLOT (mach_, "disabled", &TheCandyMachine::onentry_disabled, &TheCandyMachine::onexit_disabled, this); 69 | 70 | //boost::function cond_slot; 71 | REGISTER_COND_SLOT(mach_, "condNoCandy", &TheCandyMachine::condNoCandy, this); 72 | REGISTER_COND_SLOT(mach_, "condNoCredit", &TheCandyMachine::condNoCredit, this); 73 | 74 | // boost::function 75 | REGISTER_ACTION_SLOT(mach_, "releaseCandy", &TheCandyMachine::releaseCandy, this); 76 | REGISTER_ACTION_SLOT(mach_, "withdrawCoins", &TheCandyMachine::withdrawCoins, this); 77 | 78 | mach_->StartEngine(); 79 | } 80 | 81 | ~TheCandyMachine () 82 | { 83 | mach_->ShutDownEngine(true); 84 | } 85 | 86 | void store_candy (int num) 87 | { 88 | num_of_candy_stored_ += num; 89 | mach_->enqueEvent("add-candy"); 90 | cout << "store " << num << " gumballs, now machine has " << num_of_candy_stored_ << " gumballs." << endl; 91 | } 92 | 93 | void insertQuater () 94 | { 95 | insert_coin(25); 96 | cout << "you insert a quarter, now credit = " << credit_ << endl; 97 | } 98 | 99 | void ejectQuater () 100 | { 101 | mach_->enqueEvent("withdraw-coin"); 102 | cout << "you pulled the eject crank" << endl; 103 | } 104 | 105 | void turnCrank () 106 | { 107 | mach_->enqueEvent("release-candy"); 108 | cout << "you turned release crank" << endl; 109 | } 110 | 111 | protected: 112 | 113 | void insert_coin (int credit) 114 | { 115 | credit_ += credit; 116 | mach_->enqueEvent("coin"); 117 | } 118 | 119 | void onentry_idle () 120 | { 121 | cout << "onentry_idle" << endl; 122 | cout << "Machine is waiting for quarter" << endl; 123 | if (num_of_candy_stored_ == 0) { 124 | mach_->enqueEvent ("empty"); 125 | } 126 | } 127 | 128 | void onexit_idle () 129 | { 130 | cout << "onexit_idle" << endl; 131 | } 132 | 133 | void onentry_active () 134 | { 135 | cout << "onentry_active" << endl; 136 | } 137 | 138 | void onexit_active () 139 | { 140 | cout << "onexit_active" << endl; 141 | } 142 | 143 | void onentry_releasing () 144 | { 145 | cout << "onentry_releasing" << endl; 146 | //PunctualFrameMover::registerTimedAction(1.0, boost::bind(&TheCandyMachine::candy_released, this)); 147 | candy_released (); 148 | } 149 | 150 | void onexit_releasing () 151 | { 152 | cout << "onexit_releasing" << endl; 153 | } 154 | 155 | void onentry_disabled () 156 | { 157 | cout << "onentry_disabled" << endl; 158 | } 159 | 160 | void onexit_disabled () 161 | { 162 | cout << "onexit_disabled" << endl; 163 | } 164 | 165 | bool condNoCandy () const 166 | { 167 | return num_of_candy_stored_ == 0; 168 | } 169 | 170 | bool condNoCredit () const 171 | { 172 | return credit_ == 0; 173 | } 174 | 175 | void releaseCandy () 176 | { 177 | int num_to_release = credit_ / 25; 178 | if (num_to_release > num_of_candy_stored_) { 179 | num_to_release = num_of_candy_stored_; 180 | } 181 | cout << "release " << num_to_release << " gumballs" << endl; 182 | num_of_candy_stored_ -= num_to_release; 183 | credit_ -= num_to_release * 25; 184 | } 185 | 186 | void withdrawCoins () 187 | { 188 | cout << "there you go, the money, " << credit_ << endl; 189 | credit_ = 0; 190 | cout << "Quarter returned" << endl; 191 | } 192 | 193 | void candy_released () 194 | { 195 | mach_->enqueEvent("candy-released"); 196 | } 197 | 198 | public: 199 | 200 | void report () 201 | { 202 | cout << "\nA Candy Selling Machine\n"; 203 | cout << "Inventory: " << num_of_candy_stored_ << " gumballs\n"; 204 | cout << "Credit: " << credit_ << endl << endl; 205 | } 206 | 207 | void init () 208 | { 209 | mach_->frame_move(0); 210 | assert (mach_->inState("disabled")); 211 | this->store_candy(5); 212 | mach_->frame_move(0); 213 | assert (mach_->inState("idle")); 214 | report (); 215 | } 216 | 217 | void frame_move () 218 | { 219 | mach_->frame_move(0); 220 | } 221 | 222 | void test () 223 | { 224 | this->insertQuater(); 225 | frame_move(); 226 | this->turnCrank(); 227 | frame_move(); 228 | report (); 229 | 230 | this->insertQuater(); 231 | frame_move(); 232 | this->ejectQuater(); 233 | frame_move(); 234 | report (); 235 | this->turnCrank(); 236 | frame_move(); 237 | report (); 238 | 239 | this->insertQuater(); frame_move(); 240 | this->turnCrank(); frame_move(); 241 | this->insertQuater(); frame_move(); 242 | this->turnCrank(); frame_move(); 243 | this->ejectQuater(); frame_move(); 244 | report(); 245 | 246 | this->insertQuater(); frame_move(); 247 | this->insertQuater(); frame_move(); 248 | this->turnCrank(); frame_move(); 249 | this->insertQuater(); frame_move(); 250 | this->turnCrank(); frame_move(); 251 | this->insertQuater(); frame_move(); 252 | this->ejectQuater(); frame_move(); 253 | report(); 254 | 255 | this->store_candy(5); 256 | frame_move(); 257 | this->turnCrank(); frame_move(); 258 | report(); 259 | } 260 | }; 261 | 262 | int main(int argc, char* argv[]) 263 | { 264 | AutoReleasePool apool; 265 | #if USE_JSON 266 | StateMachineManager::instance()->set_scxml("cm_scxml", cm_json); 267 | #else 268 | StateMachineManager::instance()->set_scxml("cm_scxml", cm_scxml); 269 | #endif 270 | // StateMachineManager::instance()->set_scxml_file("cm_scxml", "cm.scxml"); // optionally through a file 271 | // StateMachineManager::instance()->prepare_machs(); // optionally load all scxml at once or getMach() on the fly 272 | { 273 | TheCandyMachine mach; 274 | mach.init (); 275 | mach.test (); 276 | } 277 | StateMachineManager::instance()->pumpMachEvents(); 278 | StateMachineManager::instance()->release_instance(); 279 | AutoReleasePool::pumpPools(); 280 | 281 | return 0; 282 | } 283 | -------------------------------------------------------------------------------- /scm/tests/test-HistoryMachine.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | using namespace std; 7 | using namespace scm; 8 | 9 | #define USE_XML 1 10 | 11 | std::string watch_scxml = "\ 12 | \ 13 | \ 14 | \ 15 | \ 16 | \ 17 | \ 18 | \ 19 | \ 20 | \ 21 | \ 22 | \ 23 | \ 24 | \ 25 | \ 26 | \ 27 | \ 28 | \ 29 | \ 30 | \ 31 | \ 32 | \ 33 | \ 34 | \ 35 | \ 36 | \ 37 | \ 38 | \ 39 | \ 40 | \ 41 | \ 42 | \ 43 | \ 44 | \ 45 | \ 46 | \ 47 | \ 48 | \ 49 | \ 50 | \ 51 | \ 52 | \ 53 | \ 54 | \ 55 | \ 56 | \ 57 | \ 58 | \ 59 | \ 60 | \ 61 | \ 62 | \ 63 | \ 64 | \ 65 | \ 66 | \ 67 | \ 68 | \ 69 | \ 70 | \ 71 | \ 72 | \ 73 | \ 74 | \ 75 | \ 76 | \ 77 | \ 78 | \ 79 | \ 80 | \ 81 | \ 82 | \ 83 | \ 84 | \ 85 | \ 86 | \ 87 | \ 88 | \ 89 | \ 90 | "; 91 | 92 | std::string watch_json = "{\n\ 93 | 'scxml': {\n\ 94 | 'non-unique': 'on,off',\n\ 95 | 'state': {\n\ 96 | 'id': 'time', \n\ 97 | 'transition': {\n\ 98 | 'event':'a',\n\ 99 | 'target':'alarm1' \n\ 100 | },\n\ 101 | 'transition': {\n\ 102 | 'event':'c_down',\n\ 103 | 'target': 'wait'\n\ 104 | }\n\ 105 | },\n\ 106 | 'state': {\n\ 107 | 'id': 'wait', \n\ 108 | 'transition': {\n\ 109 | 'event':'c_up',\n\ 110 | 'target': 'time' \n\ 111 | },\n\ 112 | 'transition': {\n\ 113 | 'event': '2_sec',\n\ 114 | 'target': 'update'\n\ 115 | }\n\ 116 | },\n\ 117 | 'state': {\n\ 118 | 'id':'update', \n\ 119 | 'history': {\n\ 120 | 'id':'histu', \n\ 121 | 'type': 'shallow'\n\ 122 | },\n\ 123 | 'transition': {\n\ 124 | 'event': 'd',\n\ 125 | 'target': 'histu' \n\ 126 | },\n\ 127 | 'state': {\n\ 128 | 'id':'sec', \n\ 129 | 'transition': {\n\ 130 | 'event':'c',\n\ 131 | 'target':'1min' \n\ 132 | }\n\ 133 | },\n\ 134 | 'state': {\n\ 135 | 'id': '1min', \n\ 136 | 'transition': {\n\ 137 | 'event':'c',\n\ 138 | 'target': '10min' \n\ 139 | }\n\ 140 | },\n\ 141 | 'state': {\n\ 142 | 'id':'10min', \n\ 143 | 'transition': {\n\ 144 | 'event': 'c',\n\ 145 | 'target':'hr' \n\ 146 | }\n\ 147 | },\n\ 148 | 'state': {\n\ 149 | 'id':'hr', \n\ 150 | 'transition': {\n\ 151 | 'event':'c',\n\ 152 | 'target':'time' \n\ 153 | }\n\ 154 | }\n\ 155 | }, \n\ 156 | 'state': {\n\ 157 | 'id':'alarm1',\n\ 158 | 'history':'shallow', \n\ 159 | 'transition': {\n\ 160 | 'event':'a',\n\ 161 | 'target':'alarm2' \n\ 162 | },\n\ 163 | 'state':{\n\ 164 | 'id':'off', \n\ 165 | 'transition': {\n\ 166 | 'event':'d',\n\ 167 | 'target':'on' \n\ 168 | }\n\ 169 | },\n\ 170 | 'state': {\n\ 171 | 'id':'on', \n\ 172 | 'transition': {\n\ 173 | 'event':'d',\n\ 174 | 'target':'off' \n\ 175 | }\n\ 176 | }\n\ 177 | }, \n\ 178 | 'state': {\n\ 179 | 'id':'alarm2',\n\ 180 | 'history':'shallow', \n\ 181 | 'transition': {\n\ 182 | 'event':'a',\n\ 183 | 'target':'chime' \n\ 184 | },\n\ 185 | 'state': {\n\ 186 | 'id':'off', \n\ 187 | 'transition': {\n\ 188 | 'event':'d',\n\ 189 | 'target':'on' \n\ 190 | }\n\ 191 | },\n\ 192 | 'state': {\n\ 193 | 'id':'on', \n\ 194 | 'transition':{\n\ 195 | 'event':'d',\n\ 196 | 'target':'off' \n\ 197 | }\n\ 198 | } \n\ 199 | },\n\ 200 | 'state': {\n\ 201 | 'id':'chime',\n\ 202 | 'history':'shallow', \n\ 203 | 'transition': {\n\ 204 | 'event':'a',\n\ 205 | 'target':'stopwatch' \n\ 206 | },\n\ 207 | 'state': {\n\ 208 | 'id':'off', \n\ 209 | 'transition': {\n\ 210 | 'event':'d',\n\ 211 | 'target':'on' \n\ 212 | }\n\ 213 | },\n\ 214 | 'state': {\n\ 215 | 'id':'on', \n\ 216 | 'transition': {\n\ 217 | 'event':'d',\n\ 218 | 'target':'off'\n\ 219 | }\n\ 220 | }\n\ 221 | },\n\ 222 | 'state': {\n\ 223 | 'id':'stopwatch',\n\ 224 | 'history':'deep', \n\ 225 | 'transition': { 'event':'a', 'target':'time'}, \n\ 226 | 'state': { 'id':'zero', \n\ 227 | 'transition': { 'event':'b', 'target':'on,regular'} \n\ 228 | }, \n\ 229 | 'parallel': { \n\ 230 | 'state': { 'id':'run', \n\ 231 | 'state': { 'id':'on', \n\ 232 | 'transition': { 'event':'b', 'target':'off'} \n\ 233 | }, \n\ 234 | 'state': { 'id':'off', \n\ 235 | 'transition': { 'event':'b', 'target':'on'} \n\ 236 | } \n\ 237 | }, \n\ 238 | 'state': { 'id':'display', \n\ 239 | 'state': { 'id':'regular', \n\ 240 | 'transition': { 'event':'d', 'cond':'In(on)', 'target':'lap'}, \n\ 241 | 'transition': { 'event':'d', 'cond':'In(off)', 'target':'zero'} \n\ 242 | }, \n\ 243 | 'state': { 'id':'lap', \n\ 244 | 'transition': { 'event':'d', 'target':'regular'} \n\ 245 | } \n\ 246 | } \n\ 247 | } \n\ 248 | } \n\ 249 | }\n\ 250 | }"; 251 | 252 | class TheMachine : public Uncopyable 253 | { 254 | StateMachine *mach_; 255 | int hr_; 256 | int min_; 257 | int sec_; 258 | 259 | public: 260 | TheMachine() 261 | : hr_(0) 262 | , min_(0) 263 | , sec_(0) 264 | { 265 | mach_ = StateMachineManager::instance()->getMach("watch"); 266 | mach_->retain(); 267 | vector const&states = mach_->get_all_states(); 268 | cout << "we have states: " << endl; 269 | for (size_t state_idx=0; state_idx < states.size(); ++state_idx) { 270 | State *st = mach_->getState(states[state_idx]); 271 | if (st == 0) continue; // <- state machine itself 272 | for (int i=0; i < st->depth(); ++i) { 273 | cout << " "; 274 | } 275 | cout << states[state_idx] << endl; 276 | mach_->setActionSlot ("onentry_" + states[state_idx], boost::bind (&TheMachine::onentry_report_state, this, false)); 277 | mach_->setActionSlot ("onexit_" + states[state_idx], boost::bind (&TheMachine::onexit_report_state, this, states[state_idx])); 278 | } 279 | cout << endl; 280 | mach_->setActionSlot ("onentry_sec", boost::bind (&TheMachine::onentry_sec, this)); 281 | mach_->setActionSlot ("onentry_1min", boost::bind (&TheMachine::onentry_1min, this)); 282 | mach_->setActionSlot ("onentry_10min", boost::bind (&TheMachine::onentry_10min, this)); 283 | mach_->setActionSlot ("onentry_hr", boost::bind (&TheMachine::onentry_hr, this)); 284 | mach_->StartEngine(); 285 | } 286 | 287 | ~TheMachine () 288 | { 289 | mach_->release(); 290 | } 291 | 292 | void onentry_sec() 293 | { 294 | if (mach_->re_enter_state()) { 295 | sec_ = 0; 296 | } 297 | onentry_report_state(true); 298 | } 299 | 300 | void onentry_1min() 301 | { 302 | if (mach_->re_enter_state()) { 303 | ++min_; 304 | } 305 | onentry_report_state(true); 306 | } 307 | 308 | void onentry_10min() 309 | { 310 | if (mach_->re_enter_state()) { 311 | min_ += 10; 312 | } 313 | onentry_report_state(true); 314 | } 315 | 316 | void onentry_hr() 317 | { 318 | if (mach_->re_enter_state()) { 319 | ++hr_; 320 | } 321 | onentry_report_state(true); 322 | } 323 | 324 | void onentry_report_state(bool with_time) 325 | { 326 | cout << "enter state " << mach_->getEnterState()->state_uid(); 327 | //if (with_time) { 328 | cout << ". time " << hr_ << ":" << min_ << ":" << sec_; 329 | //} 330 | cout << endl; 331 | ++sec_; 332 | } 333 | 334 | void onexit_report_state(std::string const&st) 335 | { 336 | cout << "exit state " << st << endl; 337 | } 338 | 339 | void test () 340 | { 341 | // time 342 | mach_->enqueEvent("c_down"); // -> wait 343 | mach_->enqueEvent("2_sec"); // -> update, you will use registerTimedEvent to generate event after 2 seconds 344 | mach_->enqueEvent("d"); // reset, 1 second 345 | mach_->enqueEvent("d"); // reset, 1 second 346 | mach_->enqueEvent("d"); // reset, 1 second 347 | mach_->enqueEvent("c"); // -> 1min state 348 | mach_->enqueEvent("d"); // 1 min 349 | mach_->enqueEvent("d"); // 2 min 350 | mach_->enqueEvent("c"); // -> 10min state 351 | mach_->enqueEvent("d"); // 12 min 352 | mach_->enqueEvent("c"); // -> hr state 353 | mach_->enqueEvent("d"); // 1 hr 354 | mach_->enqueEvent("d"); // 2 hr 355 | mach_->enqueEvent("c"); // -> time 356 | 357 | mach_->enqueEvent("a"); // -> alarm1 358 | mach_->enqueEvent("d"); 359 | mach_->enqueEvent("a"); // -> alarm2 360 | mach_->enqueEvent("d"); 361 | mach_->enqueEvent("a"); // -> chime 362 | mach_->enqueEvent("d"); 363 | mach_->enqueEvent("a"); // -> stopwatch.zero 364 | mach_->enqueEvent("b"); // -> run.on 365 | mach_->frame_move(0); 366 | cout << "check point:" << endl; // should display 'run.on' and 'regular' because it's in parallel state. 367 | vector states = mach_->getCurrentStateUId(); 368 | for (size_t i=0; i < states.size(); ++i) { 369 | cout << "in state " << states[i] << endl; 370 | } 371 | mach_->enqueEvent("d"); // -> display.lap 372 | mach_->enqueEvent("a"); // -> time 373 | mach_->enqueEvent("d"); // no effect 374 | mach_->enqueEvent("a"); // -> alarm1 375 | mach_->enqueEvent("d"); 376 | mach_->enqueEvent("a"); // -> alarm2 377 | mach_->enqueEvent("d"); 378 | mach_->enqueEvent("a"); // -> chime 379 | mach_->enqueEvent("d"); 380 | mach_->enqueEvent("a"); // -> stopwatch, what's run and display in? 381 | 382 | mach_->frame_move(0); 383 | } 384 | }; 385 | 386 | int main(int argc, char* argv[]) 387 | { 388 | AutoReleasePool apool; 389 | #if USE_XML 390 | StateMachineManager::instance()->set_scxml("watch", watch_scxml); 391 | #else 392 | StateMachineManager::instance()->set_scxml("watch", watch_json); 393 | #endif 394 | { 395 | TheMachine mach; 396 | mach.test (); 397 | } 398 | StateMachineManager::instance()->pumpMachEvents(); 399 | StateMachineManager::instance()->release_instance(); 400 | AutoReleasePool::pumpPools(); 401 | return 0; 402 | } 403 | -------------------------------------------------------------------------------- /scm/tests/test-StateMachine.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | using namespace std; 7 | using namespace scm; 8 | 9 | std::string client_scxml = "\ 10 | \ 11 | \ 12 | \ 13 | \ 14 | \ 15 | \ 16 | \ 17 | \ 18 | \ 19 | \ 20 | \ 21 | \ 22 | \ 23 | \ 24 | \ 25 | \ 26 | \ 27 | \ 28 | \ 29 | \ 30 | \ 31 | \ 32 | \ 33 | \ 34 | \ 35 | \ 36 | \ 37 | \ 38 | \ 39 | \ 40 | \ 41 | \ 42 | \ 43 | ' \ 44 | \ 45 | \ 46 | "; 47 | 48 | std::string client_json = "{\ 49 | 'scxml': {\ 50 | 'state': {\ 51 | 'id': 'new', \ 52 | 'transition': {\ 53 | 'event': 'punch',\ 54 | 'target': 'punching'\ 55 | }\ 56 | },\ 57 | 'parallel': {\ 58 | 'id': 'live', \ 59 | 'state': {\ 60 | 'id': 'punch', \ 61 | 'transition': {\ 62 | 'event': 'fail',\ 63 | 'target': 'punch_fail'\ 64 | },\ 65 | 'state': {\ 66 | 'id': 'punching',\ 67 | 'transition': {\ 68 | 'event': 'linked',\ 69 | 'target': 'linked'\ 70 | }\ 71 | },\ 72 | 'state': {\ 73 | 'id': 'linked',\ 74 | 'transition': {\ 75 | 'event': 'established',\ 76 | 'target': 'punch_success'\ 77 | }\ 78 | },\ 79 | 'state': {\ 80 | 'id': 'punch_success'\ 81 | },\ 82 | 'state': {\ 83 | 'id': 'punch_fail',\ 84 | 'transition': {\ 85 | 'event': 'try_again', \ 86 | 'target': 'punching' \ 87 | }\ 88 | }\ 89 | },\ 90 | 'state': {\ 91 | 'id': 'mode',\ 92 | 'state': {\ 93 | 'id': 'relay',\ 94 | 'state': {\ 95 | 'id':'relay_init',\ 96 | 'transition': {\ 97 | 'event': 'cipher_ready',\ 98 | 'target': 'relay_work'\ 99 | }\ 100 | },\ 101 | 'state': {\ 102 | 'id': 'relay_work', \ 103 | 'transition': {\ 104 | 'cond': 'In(punch_success)',\ 105 | 'target': 'established'\ 106 | }\ 107 | }\ 108 | },\ 109 | 'state': {\ 110 | 'id': 'established'\ 111 | }\ 112 | }\ 113 | },\ 114 | 'transition': {\ 115 | 'event': 'close',\ 116 | 'target': 'closed'\ 117 | },\ 118 | 'final': {\ 119 | 'id': 'closed'\ 120 | }\ 121 | }\ 122 | }\ 123 | "; 124 | 125 | class TheMachine : public Uncopyable 126 | { 127 | StateMachine *mach_; 128 | 129 | public: 130 | TheMachine() 131 | { 132 | mach_ = StateMachineManager::instance()->getMach("test-machine"); 133 | mach_->retain(); 134 | REGISTER_STATE_SLOT (mach_, "new", &TheMachine::onentry_new, &TheMachine::onexit_new, this); 135 | REGISTER_STATE_SLOT (mach_, "live", &TheMachine::onentry_live, &TheMachine::onexit_live, this); 136 | REGISTER_STATE_SLOT (mach_, "punch", &TheMachine::onentry_punch, &TheMachine::onexit_punch, this); 137 | REGISTER_STATE_SLOT (mach_, "punching", &TheMachine::onentry_punching, &TheMachine::onexit_punching, this); 138 | REGISTER_STATE_SLOT (mach_, "linked", &TheMachine::onentry_linked, &TheMachine::onexit_linked, this); 139 | REGISTER_STATE_SLOT (mach_, "punch_success", &TheMachine::onentry_punch_success, &TheMachine::onexit_punch_success, this); 140 | REGISTER_STATE_SLOT (mach_, "punch_fail", &TheMachine::onentry_punch_fail, &TheMachine::onexit_punch_fail, this); 141 | REGISTER_STATE_SLOT (mach_, "mode", &TheMachine::onentry_mode, &TheMachine::onexit_mode, this); 142 | REGISTER_STATE_SLOT (mach_, "relay", &TheMachine::onentry_relay, &TheMachine::onexit_relay, this); 143 | REGISTER_STATE_SLOT (mach_, "relay_init", &TheMachine::onentry_relay_init, &TheMachine::onexit_relay_init, this); 144 | REGISTER_STATE_SLOT (mach_, "relay_work", &TheMachine::onentry_relay_work, &TheMachine::onexit_relay_work, this); 145 | REGISTER_STATE_SLOT (mach_, "established", &TheMachine::onentry_established, &TheMachine::onexit_established, this); 146 | REGISTER_STATE_SLOT (mach_, "closed", &TheMachine::onentry_closed, &TheMachine::onexit_closed, this); 147 | 148 | mach_->StartEngine(); 149 | } 150 | 151 | ~TheMachine () 152 | { 153 | mach_->release(); 154 | } 155 | 156 | void onentry_new () 157 | { 158 | cout << "onentry_new" << endl; 159 | } 160 | 161 | void onexit_new () 162 | { 163 | cout << "onexit_new" << endl; 164 | } 165 | 166 | void onentry_live () 167 | { 168 | cout << "onentry_live" << endl; 169 | } 170 | 171 | void onexit_live () 172 | { 173 | cout << "onexit_live" << endl; 174 | } 175 | 176 | void onentry_punch () 177 | { 178 | cout << "onentry_punch" << endl; 179 | } 180 | 181 | void onexit_punch () 182 | { 183 | cout << "onexit_punch" << endl; 184 | } 185 | 186 | void onentry_punching () 187 | { 188 | cout << "onentry_punching" << endl; 189 | } 190 | 191 | void onexit_punching () 192 | { 193 | cout << "onexit_punching" << endl; 194 | } 195 | 196 | void onentry_linked () 197 | { 198 | cout << "onentry_linked" << endl; 199 | } 200 | 201 | void onexit_linked () 202 | { 203 | cout << "onexit_linked" << endl; 204 | } 205 | 206 | void onentry_punch_success () 207 | { 208 | cout << "onentry_punch_success" << endl; 209 | } 210 | 211 | void onexit_punch_success () 212 | { 213 | cout << "onexit_punch_success" << endl; 214 | } 215 | 216 | void onentry_punch_fail () 217 | { 218 | cout << "onentry_punch_fail" << endl; 219 | } 220 | 221 | void onexit_punch_fail () 222 | { 223 | cout << "onexit_punch_fail" << endl; 224 | } 225 | 226 | void onentry_mode () 227 | { 228 | cout << "onentry_mode" << endl; 229 | } 230 | 231 | void onexit_mode () 232 | { 233 | cout << "onexit_mode" << endl; 234 | } 235 | 236 | void onentry_relay () 237 | { 238 | cout << "onentry_relay" << endl; 239 | } 240 | 241 | void onexit_relay () 242 | { 243 | cout << "onexit_relay" << endl; 244 | } 245 | 246 | void onentry_relay_init () 247 | { 248 | cout << "onentry_relay_init" << endl; 249 | } 250 | 251 | void onexit_relay_init () 252 | { 253 | cout << "onexit_relay_init" << endl; 254 | } 255 | 256 | void onentry_relay_work () 257 | { 258 | cout << "onentry_relay_work" << endl; 259 | } 260 | 261 | void onexit_relay_work () 262 | { 263 | cout << "onexit_relay_work" << endl; 264 | } 265 | 266 | void onentry_established () 267 | { 268 | cout << "onentry_established" << endl; 269 | } 270 | 271 | void onexit_established () 272 | { 273 | cout << "onexit_established" << endl; 274 | } 275 | 276 | void onentry_closed () 277 | { 278 | cout << "onentry_closed" << endl; 279 | } 280 | 281 | void onexit_closed () 282 | { 283 | assert(0 && "exit final"); 284 | cout << "onexit_closed" << endl; 285 | } 286 | 287 | void test () 288 | { 289 | mach_->enqueEvent("punch"); 290 | mach_->enqueEvent("linked"); 291 | mach_->frame_move(0); 292 | } 293 | }; 294 | 295 | int main(int argc, char* argv[]) 296 | { 297 | AutoReleasePool apool; 298 | #if USE_XML 299 | StateMachineManager::instance()->set_scxml("test-machine", client_scxml); 300 | #else 301 | StateMachineManager::instance()->set_scxml("test-machine", client_json); 302 | #endif 303 | { 304 | TheMachine mach; 305 | mach.test (); 306 | } 307 | StateMachineManager::instance()->pumpMachEvents(); 308 | StateMachineManager::instance()->release_instance(); 309 | AutoReleasePool::pumpPools(); 310 | return 0; 311 | } 312 | -------------------------------------------------------------------------------- /scm/uncopyable.h: -------------------------------------------------------------------------------- 1 | #ifndef SCM_UNCOPYABLE_H 2 | #define SCM_UNCOPYABLE_H 3 | 4 | namespace scm { 5 | 6 | class Uncopyable 7 | { 8 | protected: 9 | Uncopyable () {} 10 | ~Uncopyable () {} 11 | private: 12 | Uncopyable (Uncopyable const&rhs); 13 | Uncopyable &operator=(Uncopyable const&rhs); 14 | }; 15 | 16 | } 17 | 18 | #endif --------------------------------------------------------------------------------