1 /******************************************************************************* 2 * Flow module for state transion 3 * 4 * Copyright: © 2019, SHOO 5 * License: [BSL-1.0](http://boost.org/LICENSE_1_0.txt). 6 * Author: SHOO 7 */ 8 module cushion.flow; 9 10 import cushion.handler, cushion._internal.misc; 11 12 private interface FlowHandler(Commands) 13 { 14 protected: 15 /// 16 void _onEnterChild(Commands child) @safe; 17 /// 18 void _onExitChild(Commands child) @safe; 19 /// 20 void _onEnter() @safe; 21 /// 22 void _onExit() @safe; 23 } 24 25 /******************************************************************************* 26 * Flow template class 27 * 28 * Flow is a template class that holds the state based on the 'state pattern'. 29 * The interface of the command set whose behavior changes depending on the state is given by the Commands parameter. 30 * The default base class of this template class is Object, but it can be specified by `Base`. 31 * The initial state of the concrete instance of Commands is given to the constructor of this template class, 32 * and transition is made according to the return value of each command. 33 * If the command returns null, the transition is ended. 34 */ 35 template Flow(Commands, Base = Object, 36 EnterChildHandler = void delegate(Commands, Commands)[], 37 ExitChildHandler = void delegate(Commands, Commands)[], 38 EndFlowHandler = void delegate(Commands)[]) 39 if (is(Commands == interface) 40 && isHandler!EnterChildHandler 41 && isHandler!ExitChildHandler 42 && isHandler!EndFlowHandler) 43 { 44 alias FlowHandler = .FlowHandler!Commands; 45 /// 46 abstract class FlowBase: Base 47 { 48 private: 49 import std.container; 50 SList!Commands _stsStack; 51 Commands _next; 52 // 53 void _onEnterChild(Commands parent, Commands child) @safe 54 { 55 if (auto p = trustedCast!FlowHandler(parent)) 56 { 57 p._onEnterChild(child); 58 } 59 if (auto c = trustedCast!FlowHandler(child)) 60 { 61 c._onEnter(); 62 } 63 onEnterChild.call(parent, child); 64 } 65 // 66 void _onExitChild(Commands parent, Commands child) @safe 67 { 68 if (auto c = trustedCast!FlowHandler(child)) 69 { 70 c._onExit(); 71 } 72 if (auto p = trustedCast!FlowHandler(parent)) 73 { 74 p._onExitChild(child); 75 } 76 onExitChild.call(parent, child); 77 } 78 // 79 void _onEndFlow(Commands last) @safe 80 { 81 onEndFlow.call(last); 82 } 83 public: 84 /// 85 EnterChildHandler onEnterChild; 86 /// 87 ExitChildHandler onExitChild; 88 /// 89 EndFlowHandler onEndFlow; 90 91 /// 92 this(Commands root) @safe 93 { 94 _stsStack.insertFront(root); 95 _next = trustedCast!Commands(this); 96 } 97 /// 98 final inout(Commands) current() @trusted inout @property 99 { 100 return _stsStack.empty ? null : cast(inout)(*cast(SList!Commands*)&_stsStack).front; 101 } 102 protected: 103 /// internal 104 final Commands _transit(Commands curr, Commands nxt) @safe 105 { 106 if (nxt is null) 107 { 108 _stsStack.removeFront(); 109 if (_stsStack.empty) 110 { 111 _onEndFlow(curr); 112 } 113 else 114 { 115 _onExitChild(_stsStack.front, curr); 116 } 117 } 118 else if (trustedCast!Object(curr) !is trustedCast!Object(nxt)) 119 { 120 _stsStack.insertFront(nxt); 121 _onEnterChild(curr, nxt); 122 } 123 else 124 { 125 // Do nothing 126 } 127 return _next; 128 } 129 } 130 131 import std.traits: ReturnType; 132 import std.typecons: AutoImplement; 133 enum generateTransferFunction(C, alias fun) = 134 ` 135 auto curr = current; 136 auto res = curr.` ~ __traits(identifier, fun) ~ `(args); 137 return _transit(curr, res); 138 `; 139 140 enum isEventDistributor(alias func) = is(ReturnType!func: Commands); 141 142 alias Flow = AutoImplement!(Commands, FlowBase, generateTransferFunction, isEventDistributor); 143 } 144 145 146 /// ditto 147 template Flow(alias Policy) 148 if (!is(Policy == interface) 149 && __traits(hasMember, Policy, "Commands") 150 && is(Policy.Commands == interface)) 151 { 152 alias Commands = Policy.Commands; 153 alias Flow = .Flow!(Commands, 154 getMemberAlias!(Policy, "Base", Object), 155 getMemberAlias!(Policy, "EnterChildHandler", void delegate(Commands, Commands)[]), 156 getMemberAlias!(Policy, "ExitChildHandler", void delegate(Commands, Commands)[]), 157 getMemberAlias!(Policy, "EndFlowHandler", void delegate(Commands)[])); 158 } 159 160 /// 161 @safe unittest 162 { 163 interface TestCommand 164 { 165 TestCommand command(string cmd) @safe; 166 TestCommand update() @safe; 167 } 168 string msg; 169 170 TestCommand test1; 171 TestCommand test2; 172 173 test1 = new class TestCommand 174 { 175 TestCommand command(string cmd) @safe 176 { 177 msg = cmd; 178 // not transit 179 return this; 180 } 181 TestCommand update() @safe 182 { 183 msg = ""; 184 // transit to child(test2) 185 return test2; 186 } 187 }; 188 test2 = new class TestCommand 189 { 190 TestCommand command(string cmd) @safe 191 { 192 msg = cmd ~ "!"; 193 // not transit 194 return this; 195 } 196 TestCommand update() @safe 197 { 198 msg = "!"; 199 // transit to parent(test1) 200 return null; 201 } 202 }; 203 204 auto sts = new Flow!TestCommand(test1); 205 206 // not transit 207 sts.command("test"); 208 assert(msg == "test"); 209 210 // transit to child(test2) 211 sts.update(); 212 assert(msg == ""); 213 214 // not transit 215 sts.command("test"); 216 assert(msg == "test!"); 217 218 // transit to parent(test1) 219 sts.update(); 220 assert(msg == "!"); 221 222 // not transit 223 sts.command("test"); 224 assert(msg == "test"); 225 } 226 227 @safe unittest 228 { 229 interface TestCommand 230 { 231 TestCommand command(string cmd) @safe; 232 TestCommand update() @safe; 233 } 234 string msg; 235 236 TestCommand test1; 237 TestCommand test2; 238 239 struct Policy 240 { 241 alias Commands = TestCommand; 242 alias EnterChildHandler = void delegate(Commands, Commands); 243 } 244 245 test1 = new class TestCommand 246 { 247 TestCommand command(string cmd) 248 { 249 msg = cmd; 250 // not transit 251 return this; 252 } 253 TestCommand update() 254 { 255 msg = ""; 256 // transit to child(test2) 257 return test2; 258 } 259 }; 260 test2 = new class TestCommand 261 { 262 TestCommand command(string cmd) 263 { 264 msg = cmd ~ "!"; 265 // not transit 266 return this; 267 } 268 TestCommand update() 269 { 270 msg = "!"; 271 // transit to parent(test1) 272 return null; 273 } 274 }; 275 276 auto sts = new Flow!Policy(test1); 277 278 // not transit 279 sts.command("test"); 280 assert(msg == "test"); 281 282 // transit to child(test2) 283 sts.update(); 284 assert(msg == ""); 285 286 // not transit 287 sts.command("test"); 288 assert(msg == "test!"); 289 290 // transit to parent(test1) 291 sts.update(); 292 assert(msg == "!"); 293 294 // not transit 295 sts.command("test"); 296 assert(msg == "test"); 297 } 298 299 300 301 302 303 /******************************************************************************* 304 * Base template class of state derived Commands 305 * 306 * This template class provides several convenience handlers and methods. 307 * To use this, instantiate this template class with Commands and inherit. 308 * In derived classes, the return value of each method of Commands is obtained by 309 * transferring the processing to the super class. 310 */ 311 template State(Commands, Base=Object, 312 EnterChildHandler = void delegate(Commands)[], 313 ExitChildHandler = EnterChildHandler, 314 EnterHandler = void delegate()[], 315 ExitHandler = EnterHandler) 316 if (is(Commands == interface) 317 && isHandler!EnterChildHandler 318 && isHandler!ExitChildHandler 319 && isHandler!EnterHandler 320 && isHandler!ExitHandler) 321 { 322 /// 323 abstract class StateBase: Base, Commands, FlowHandler!Commands 324 { 325 private: 326 Commands _next; 327 protected: 328 /// internal 329 Commands _getNext() pure nothrow @nogc @safe 330 { 331 scope (exit) 332 _next = this; 333 return _next; 334 } 335 /// ditto 336 void _onEnterChild(Commands child) @safe 337 { 338 onEnterChild.call(child); 339 } 340 /// ditto 341 void _onExitChild(Commands child) @safe 342 { 343 onExitChild.call(child); 344 } 345 /// ditto 346 void _onEnter() @safe 347 { 348 onEnter.call(); 349 } 350 /// ditto 351 void _onExit() @safe 352 { 353 onExit.call(); 354 } 355 public: 356 /// Constructor 357 this() pure nothrow @nogc @safe 358 { 359 _next = this; 360 } 361 /// Handler that will be called back when flow enter child state 362 EnterChildHandler onEnterChild; 363 /// Handler that will be called back when flow exit child state 364 ExitChildHandler onExitChild; 365 /// Handler that will be called back when flow enter this state 366 EnterHandler onEnter; 367 /// Handler that will be called back when flow exit this state 368 ExitHandler onExit; 369 /// Indicates the next state. The specified state becomes a child of this state. 370 void setNext(Commands cmd) pure nothrow @nogc @safe 371 { 372 _next = cmd; 373 } 374 } 375 376 import std.traits: ReturnType; 377 import std.typecons: AutoImplement; 378 enum generateCommandFunction(C, alias fun) = `return _getNext();`; 379 enum isEventDistributor(alias func) = is(ReturnType!func: Commands); 380 alias State = AutoImplement!(Commands, StateBase, generateCommandFunction, isEventDistributor); 381 } 382 383 /// ditto 384 template State(alias Policy) 385 if (!is(Policy == interface) 386 && __traits(hasMember, Policy, "Commands") 387 && is(Policy.Commands == interface)) 388 { 389 alias Commands = Policy.Commands; 390 alias State = .State!(Commands, 391 getMemberAlias!(Policy, "Base", Object), 392 getMemberAlias!(Policy, "EnterChildHandler", void delegate(Commands)), 393 getMemberAlias!(Policy, "ExitChildHandler", void delegate(Commands)), 394 getMemberAlias!(Policy, "EnterHandler", void delegate()), 395 getMemberAlias!(Policy, "ExitHandler", void delegate())); 396 } 397 398 399 400 /// 401 @safe unittest 402 { 403 interface TestCommand 404 { 405 TestCommand command(string cmd) @safe; 406 TestCommand update() @safe; 407 } 408 string msg; 409 410 TestCommand test1; 411 TestCommand test2; 412 413 static struct StatePolicy 414 { 415 alias Commands = TestCommand; 416 alias ExitChildHandler = void delegate(Commands); 417 } 418 static struct FlowPolicy 419 { 420 alias Commands = TestCommand; 421 alias EnterChildHandler = void delegate(Commands, Commands); 422 } 423 test1 = new class State!StatePolicy 424 { 425 override TestCommand command(string cmd) 426 { 427 msg = cmd; 428 // not transit 429 return super.command(cmd); 430 } 431 override TestCommand update() 432 { 433 msg = ""; 434 // transit to child(test2) 435 setNext(test2); 436 return super.update(); 437 } 438 }; 439 test2 = new class State!TestCommand 440 { 441 override TestCommand command(string cmd) 442 { 443 msg = cmd ~ "!"; 444 // not transit 445 return super.command(cmd); 446 } 447 override TestCommand update() 448 { 449 msg = "!"; 450 // transit to parent(test1) 451 setNext(test1); 452 return super.update(); 453 } 454 }; 455 456 auto sts = new Flow!FlowPolicy(test1); 457 458 // not transit 459 sts.command("test"); 460 assert(msg == "test"); 461 462 // transit to child(test2) 463 sts.update(); 464 assert(msg == ""); 465 466 // not transit 467 sts.command("test"); 468 assert(msg == "test!"); 469 470 // transit to parent(test1) 471 sts.update(); 472 assert(msg == "!"); 473 474 // not transit 475 sts.command("test"); 476 assert(msg == "test"); 477 } 478