1 /**
2  * Retrograde Engine
3  *
4  * Authors:
5  *  Mike Bierlee, m.bierlee@lostmoment.com
6  * Copyright: 2014-2021 Mike Bierlee
7  * License:
8  *  This software is licensed under the terms of the MIT license.
9  *  The full terms of the license can be found in the LICENSE.txt file.
10  */
11 
12 module retrograde.messaging;
13 
14 import retrograde.stringid;
15 
16 import std.format;
17 import std.signals;
18 import std.conv;
19 import std.exception;
20 
21 interface MessageData {}
22 
23 struct Message {
24     StringId type;
25     double magnitude;
26     MessageData data;
27 }
28 
29 alias Event = Message;
30 alias Command = Message;
31 
32 class EventHandler {
33     public bool handleJoystickEvents;
34     public bool handleMouseEvents;
35     public bool handleKeyboardEvents;
36 
37     public abstract void handleEvents();
38 }
39 
40 abstract class MessageChannel {
41     mixin Signal!(const(Message));
42 }
43 
44 abstract class EventChannel : MessageChannel {}
45 
46 abstract class CommandChannel : MessageChannel {}
47 
48 struct MessageRoute {
49     StringId type;
50     MessageChannel source;
51     MessageChannel target;
52 }
53 
54 class MessageRouter {
55     private MessageRoute[StringId] routes;
56     private bool[MessageChannel] sources;
57     private const(Message) function(const(Message))[MessageRoute] adjusters;
58 
59     public void addRoute(MessageRoute route, const(Message) function(const(Message)) messageAdjuster = null) {
60         enforce(route.source !is null, "Route's source channel cannot be null");
61         enforce(route.target !is null, "Route's target channel cannot be null");
62         routes[route.type] = route;
63 
64         if (messageAdjuster !is null) {
65             adjusters[route] = messageAdjuster;
66         }
67 
68         if ((route.source in sources) is null) {
69             route.source.connect(&routeMessage);
70             sources[route.source] = true;
71         }
72     }
73 
74     private void routeMessage(const(Message) message) {
75         auto route = message.type in routes;
76         if (route) {
77             auto adjuster = *route in adjusters;
78             if (adjuster) {
79                 auto adjustedMessage = (*adjuster)(message);
80                 route.target.emit(adjustedMessage);
81             } else {
82                 route.target.emit(message);
83             }
84         }
85     }
86 }
87 
88 public const(Event) dropMessageData(const(Message) message) {
89     return Message(message.type, message.magnitude);
90 }
91 
92 class MessageProcessor {
93     private MessageChannel[] sourceChannels;
94 
95     this(MessageChannel sourceChannel) {
96         this([sourceChannel]);
97     }
98 
99     this(MessageChannel[] sourceChannels) {
100         this.sourceChannels = sourceChannels;
101     }
102 
103     public void initialize() {
104         foreach(channel; sourceChannels) {
105             channel.connect(&this.handleMessage);
106         }
107     }
108 
109     protected abstract void handleMessage(const(Message) message);
110 
111     public void update() {}
112 }