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