1 module diggler.commandqueue;
2 
3 final class CommandQueue
4 {
5 	import core.thread : Fiber;
6 
7 	import std.algorithm;
8 	import std.array;
9 	import std.exception : enforce;
10 	debug(CommandQueue) import std.stdio;
11 
12 	import diggler.context;
13 	import diggler.command;
14 
15 	private: // TODO: use std.container.Array?
16 	static struct Task
17 	{
18 		ICommandSet cmdSet;
19 		Context context;
20 		void delegate() entryPoint;
21 
22 		void prepare()
23 		{
24 			cmdSet.context = context;
25 		}
26 	}
27 
28 	Task[] queue;
29 	CommandFiber[] fibers;
30 
31 	public:
32 	class CommandFiber : Fiber
33 	{
34 		import std.typecons : Nullable;
35 		Nullable!Task currentTask;
36 		bool ready = true;
37 
38 		void run() // Never to be called directly
39 		{
40 			ready = false;
41 			scope(exit) ready = true;
42 
43 			if(!currentTask.isNull) // Can be set directly before calling fiber
44 			{
45 				currentTask.prepare();
46 				currentTask.entryPoint();
47 			}
48 
49 			while(!queue.empty)
50 			{
51 				auto task = queue.front;
52 				queue.popFront();
53 
54 				currentTask = task;
55 				task.prepare();
56 				task.entryPoint();
57 			}
58 		}
59 
60 		void resume()
61 		{
62 			debug(CommandQueue) writefln("resuming a fiber %s %s", fibers.map!(fiber => fiber.state).array, fibers.map!(fiber => fiber.ready).array);
63 			if(state == State.HOLD)
64 			{
65 				currentTask.prepare();
66 				call();
67 			}
68 		}
69 
70 		this()
71 		{
72 			super(&run);
73 		}
74 	}
75 
76 	this(size_t numFibers = 4)
77 	{
78 		fibers = new CommandFiber[](numFibers);
79 
80 		foreach(ref fiber; fibers) // TODO: grow lazily up to a max?
81 			fiber = new CommandFiber();
82 	}
83 
84 	CommandFiber fiber() @property
85 	{
86 		return enforce(cast(CommandFiber)Fiber.getThis());
87 	}
88 
89 	void post(ICommandSet cmdSet, Context ctx, void delegate() cb)
90 	{
91 		auto task = Task(cmdSet, ctx, cb);
92 
93 		import std.stdio;
94 
95 		auto availableFibers = fibers.find!(fiber => fiber.ready)();//fibers.find!(fiber => fiber.state == Fiber.State.TERM);
96 		if(!availableFibers.empty)
97 		{
98 			debug(CommandQueue) writefln("Found available fiber (#%s), running command right away %s", fibers.length - availableFibers.length + 1,
99 					 fibers.map!(fiber => fiber.ready).array);
100 			auto availableFiber = availableFibers.front;
101 			availableFiber.currentTask = task;
102 			availableFiber.reset();
103 			availableFiber.call();
104 		}
105 		else
106 		{
107 			queue ~= task;
108 			debug(CommandQueue) writeln("no available fiber, queued command");
109 		}
110 	}
111 }