1 module dash.editor.editor;
2 import dash.core.dgame;
3 import dash.editor.websockets, dash.editor.events;
4 import dash.utility.output;
5 
6 import vibe.data.json;
7 import vibe.http.status: HTTPStatus;
8 import std.uuid, std.typecons;
9 
10 /**
11  * The editor manager class. Handles all interactions with editors.
12  *
13  * May be overridden to override default event implementations.
14  */
15 class Editor
16 {
17 public:
18     /**
19      * Initializes the editor with a DGame instance.
20      *
21      * Called by DGame.
22      */
23     final void initialize( DGame instance )
24     {
25         game = instance;
26 
27         server.start( this );
28         registerDefaultEvents();
29         onInitialize();
30     }
31 
32     /**
33      * Processes pending events.
34      *
35      * Called by DGame.
36      */
37     final void update()
38     {
39         server.update();
40         processEvents();
41     }
42 
43     /**
44      * Shutsdown the editor interface.
45      *
46      * Called by DGame.
47      */
48     final void shutdown()
49     {
50         server.stop();
51     }
52 
53     /**
54      * Sends a message to all attached editors.
55      *
56      * Params:
57      *  key =           The key of the event.
58      *  value =         The data along side it.
59      */
60     final void send( DataType )( string key, DataType value )
61     {
62         send( key, value, ( Json res ) { } );
63     }
64     static assert(is(typeof( send( "key", "data" ) )));
65 
66     /**
67      * Sends a message to all attached editors.
68      *
69      * In most cases, you won't have to manually specify template parameters,
70      * they should be inferred.
71      *
72      * Params:
73      *  key =           The key of the event.
74      *  value =         The data along side it.
75      *  cb =            The callback to call when a response is received.
76      *
77      * Examples:
78      * ---
79      * // DataType inferred as string, ResponseType inferred as string.
80      * editor.send( "my_key", "my_value", ( string response ) {
81      *     // Handle response
82      * } );
83      * ---
84      */
85     final void send( DataType, ResponseType )( string key, DataType value, void delegate( ResponseType ) cb )
86     {
87         UUID cbId = randomUUID();
88 
89         void callbackHandler( EventMessage msg )
90         {
91             auto response = msg.value.deserializeJson!EventResponse;
92 
93             if( response.status == EventResponse.Status.ok )
94                 cb( response.data.deserializeJson!ResponseType );
95             else
96                 throw response.data.deserializeJson!TransferableException().toException();
97         }
98         registerCallbackHandler( cbId, &callbackHandler );
99 
100         EventMessage msg;
101         msg.key = key;
102         msg.value = value.serializeToJson();
103         msg.callbackId = cbId.toString();
104 
105         server.send( msg );
106     }
107     static assert(is(typeof( send( "key", "data", ( string response ) { } ) )));
108 
109     /**
110      * Registers an event callback, for when an event with the given key is received.
111      *
112      * Params:
113      *  key =           The key of the event.
114      *  event =         The handler to call.
115      *
116      * * Examples:
117      * ---
118      * // DataType inferred as string, ResponseType inferred as string.
119      * editor.registerEventHandler( "loopback", ( string receivedData ) {
120      *     // Handle response
121      *     // Return your response, or nothing if signify success without response.
122      *     return receivedData;
123      * } );
124      *
125      * Returns: The ID of the event, so it can be unregistered later.
126      */
127     final UUID registerEventHandler( DataType, ResponseType )( string key, ResponseType delegate( DataType ) event )
128     {
129         void handler( EventMessage msg )
130         {
131             // Automatically deserialize received data to requested type.
132             DataType receivedData;
133             try
134             {
135                 receivedData = msg.value.deserializeJson!DataType;
136             }
137             catch( JSONException e )
138             {
139                 errorf( "Error deserializing received message with key \"%s\" to %s: %s", key, DataType.stringof, e.msg );
140                 return;
141             }
142 
143             // Create a message with the callback id, and the response of the event.
144             EventMessage newMsg;
145             newMsg.key = CallbackMessageKey;
146             newMsg.callbackId = msg.callbackId;
147 
148             // Build response to send back
149             EventResponse res;
150 
151             try
152             {
153                 static if(is( ResponseType == void ))
154                 {
155                     // Call the event handler.
156                     event( receivedData );
157                     res.data = Json( "success" );
158                 }
159                 else
160                 {
161                     // Call the event handler, and capture the result.
162                     ResponseType result = event( receivedData );
163                     res.data = result.serializeToJson();
164                 }
165 
166                 // If we've made it this far, it's a success
167                 res.status = EventResponse.Status.ok;
168             }
169             catch( Exception e )
170             {
171                 // If failure, send exception.
172                 res.status = EventResponse.Status.error;
173                 res.data = TransferableException.fromException( e ).serializeToJson();
174             }
175 
176             // Serialize response, and sent it across.
177             newMsg.value = res.serializeToJson();
178             server.send( newMsg );
179         }
180 
181         return registerInternalMessageHandler( key, &handler );
182     }
183     static assert(is(typeof( registerEventHandler( "key", ( string data ) { } ) )));
184     static assert(is(typeof( registerEventHandler( "key", ( string data ) => data ) )));
185 
186     /**
187      * Unregisters an event callback.
188      *
189      * Params:
190      *  id =            The id of the handler to remove.
191      */
192     final void unregisterEventHandler( UUID id )
193     {
194         foreach( _, handlerTupArr; eventHandlers )
195         {
196             foreach( i, handlerTup; handlerTupArr )
197             {
198                 if( handlerTup.id == id )
199                 {
200                     auto end = handlerTupArr[ i+1..$ ];
201                     handlerTupArr = handlerTupArr[ 0..i ] ~ end;
202                 }
203             }
204         }
205     }
206 
207 protected:
208     DGame game;
209     WebSocketServer server;
210 
211     /// To be overridden
212     void onInitialize() { }
213     /// ditto
214     void onStartPlay() { }
215     /// ditto
216     void onPausePlay() { }
217     /// ditto
218     void onStopPlay() { }
219 
220     /**
221      * Processes all pending events.
222      *
223      * Called by update.
224      */
225     final void processEvents()
226     {
227         // Clear the events
228         scope(exit) pendingEvents.length = 0;
229 
230         foreach( event; pendingEvents )
231         {
232             // Dispatch to handlers.
233             if( auto handlerTupArray = event.key in eventHandlers )
234             {
235                 foreach( handlerTup; *handlerTupArray )
236                 {
237                     handlerTup.handler( event );
238                 }
239             }
240             else
241             {
242                 warningf( "Invalid editor event received with key %s", event.key );
243             }
244         }
245     }
246 
247 package:
248     /// The message key for callbacks
249     enum CallbackMessageKey = "__callback__";
250 
251     alias InternalEventHandler = void delegate( EventMessage );
252     alias EventHandlerTuple = Tuple!(UUID, "id", InternalEventHandler, "handler");
253 
254     /// Register an event from the front end.
255     final void queueEvent( EventMessage msg )
256     {
257         pendingEvents ~= msg;
258     }
259 
260     /// Register a message internally, after generating a handler for it.
261     final UUID registerInternalMessageHandler( string key, InternalEventHandler handler )
262     {
263         auto id = randomUUID();
264         eventHandlers[ key ] ~= EventHandlerTuple( id, handler );
265         return id;
266     }
267 
268     /// If a send call requests a callback, register it.
269     final void registerCallbackHandler( UUID id, InternalEventHandler handler )
270     {
271         callbacks[ id ] = handler;
272     }
273 
274     /// Register built-in event handlers.
275     final void registerDefaultEvents()
276     {
277         registerInternalMessageHandler( CallbackMessageKey, &handleCallback );
278 
279         // Test handler, responds with request
280         registerEventHandler!( Json, Json )( "loopback", json => json );
281 
282         registerGameEvents( this, game );
283         registerObjectEvents( this, game );
284     }
285 
286     /// Handles callback messages
287     final void handleCallback( EventMessage msg )
288     {
289         // If it's a callback, dispatch it as such.
290         UUID id = msg.callbackId.parseUUID();
291         if( id.empty )
292         {
293             error( "Callback received with empty id" );
294         }
295         else if( auto cb = id in callbacks )
296         {
297             (*cb)( msg );
298             callbacks.remove( id );
299         }
300         else
301         {
302             errorf( "Callback reference lost: %s", id );
303         }
304     }
305 
306 private:
307     EventMessage[] pendingEvents;
308     EventHandlerTuple[][string] eventHandlers;
309     InternalEventHandler[UUID] callbacks;
310 }
311 
312 /// Easy to handle response struct.
313 private struct EventResponse
314 {
315     /// Status of a request
316     enum Status
317     {
318         ok = 0,
319         error = 2,
320     }
321 
322     Status status;
323     Json data;
324 }
325 
326 // Exception that can be serialized
327 struct TransferableException
328 {
329     string msg;
330     size_t line;
331     string file;
332 
333     static TransferableException fromException( Exception e )
334     {
335         TransferableException except;
336         except.msg = e.msg;
337         except.line = e.line;
338         except.file = e.file;
339         return except;
340     }
341 
342     Exception toException()
343     {
344         return new Exception( msg, file, line );
345     }
346 }