1 module gameserver.client;
2 
3 import gameserver.common;
4 
5 import enet.enet;
6 import iopipe.json.serialize;
7 import iopipe.json.parser;
8 import iopipe.bufpipe;
9 import std.exception;
10 import core.time;
11 
12 struct Converter
13 {
14     void function(JSONTokenizer!(char[], true), size_t, void delegate() dg) process;
15     void delegate() dg;
16 }
17 
18 struct Client
19 {
20     private {
21     ENetHost *host;
22     ENetPeer *server;
23     BufferPipe msg;
24 
25     Converter[string] handlers;
26     void delegate(ClientInfo) addClientHandler;
27     void delegate(ClientInfo) removeClientHandler;
28     void delegate(ClientInfo) clientMovedHandler;
29     }
30 
31     // your id
32     ClientInfo info;
33     // list of peer clients (on the server)
34     ClientInfo[] validPeers;
35     // list of clients that are in my room.
36     ClientInfo[] roomPeers;
37 
38     void initialize(string host, ushort port) {
39         import std.string;
40         if(this.host is null)
41             this.host = enet_host_create(null, 1, 2, 0, 0);
42         ENetAddress addr;
43         addr.port = port;
44         if(enet_address_set_host(&addr, host.toStringz) < 0)
45         {
46             // error
47             throw new Exception("Error looking up host infomration for " ~ host);
48         }
49         if(server !is null)
50         {
51             if(server.address != addr)
52             {
53                 // TODO: close and open a new connection.
54                 throw new Exception("Error, client cannot be rebound without being closed first.");
55             }
56             // else, already open
57         }
58         else
59         {
60             // set up the server
61             // process client messages
62             onMsg(&processHello);
63             onMsg(&processClientAdded);
64             onMsg(&processClientRemoved);
65             onMsg(&processClientInfoChanged);
66             server = enet_host_connect(this.host, &addr, 2, 0);
67             if(server is null)
68                 throw new Exception("Error, attempt to connect to host failed");
69 
70             // wait at least 1 second for the first client message
71             while(!info.id)
72             {
73                 if(!process(1.seconds))
74                     throw new Exception("Did not get id from server!");
75             }
76         }
77 
78     }
79 
80     void disconnect()
81     {
82         if(server !is null)
83         {
84             enet_peer_disconnect_later(server, 0);
85             while(server !is null)
86                 process();
87             info = ClientInfo.init;
88         }
89     }
90     
91     private void buildRoomPeers()
92     {
93         import std.algorithm : filter;
94         import std.array : array;
95         import std.stdio;
96         roomPeers = validPeers.filter!((ref c) => c.room_id == info.room_id).array;
97         writeln("room peers is now ", roomPeers);
98     }
99 
100     private void processHello(size_t, Hello h) {
101         this.info = h.info;
102 
103         this.validPeers = h.clients;
104         buildRoomPeers();
105         import std.stdio;
106         writeln("Connected with info ", this.info, ", existing peers are ", validPeers);
107     }
108 
109     private void processClientAdded(size_t, ClientAdded ca) {
110         import std.stdio;
111         if(ca.info.id != this.info.id)
112         {
113             this.validPeers ~= ca.info;
114             // TODO this should be done via logging
115             writeln("Peer added with info ", ca.info, " valid peers now ", validPeers);
116             if(this.addClientHandler)
117                 this.addClientHandler(ca.info);
118         }
119     }
120 
121     private void processClientRemoved(size_t, ClientRemoved cr) {
122         import std.algorithm : remove, countUntil, SwapStrategy;
123         import std.stdio;
124         auto clientidx = validPeers.countUntil!((ref v, id) => v.id == id)(cr.id);
125         auto removed = validPeers[clientidx];
126         validPeers = validPeers.remove(clientidx);
127         validPeers.assumeSafeAppend;
128         buildRoomPeers();
129         writeln("Peer removed with id ", cr.id, " valid peers now ", validPeers);
130         if(this.removeClientHandler)
131             this.removeClientHandler(removed);
132     }
133 
134     private void processClientInfoChanged(size_t, ClientInfoChanged cic) {
135         import std.algorithm : find, remove, SwapStrategy;
136         import std.range : empty, front;
137         import std.stdio;
138         if(cic.info.id == info.id)
139         {
140             // I moved
141             info = cic.info;
142             buildRoomPeers();
143         }
144         else
145         {
146 
147             // otherwise, it's a peer
148             auto searched = validPeers.find!((ref ci, cid) => ci.id == cid)(cic.info.id);
149             if(searched.empty)
150                 writeln("Peer not found! ", cic.info);
151             else {
152                 auto oldroom = searched.front.room_id;
153                 searched.front = cic.info;
154                 if(oldroom == info.room_id || cic.info.room_id == info.room_id)
155                     buildRoomPeers();
156             }
157         }
158         if(clientMovedHandler)
159             clientMovedHandler(cic.info);
160     }
161 
162     void onMsg(T)(void delegate(size_t, T) dg)
163     {
164         // register a handler for type T, which will call the given delegate.
165         Converter conv;
166         conv.dg = cast(void delegate())dg;
167         conv.process = (JSONTokenizer!(char[], true) msg, size_t peerid, void delegate() callback) {
168             auto t = deserialize!T(msg);
169             (cast(void delegate(size_t, T))callback)(peerid, t);
170         };
171         handlers[T.stringof] = conv;
172     }
173 
174     void onMsg(T)(void function(size_t, T) dg)
175     {
176         import std.functional;
177         onMsg(toDelegate(dg));
178     }
179 
180     void onRemoveClient(void delegate(ClientInfo) dg)
181     {
182         removeClientHandler = dg;
183     }
184 
185     void onRemoveClient(void function(ClientInfo) dg)
186     {
187         import std.functional;
188         removeClientHandler = toDelegate(dg);
189     }
190 
191     void onAddClient(void delegate(ClientInfo) dg)
192     {
193         addClientHandler = dg;
194     }
195 
196     void onAddClient(void function(ClientInfo) dg)
197     {
198         import std.functional;
199         addClientHandler = toDelegate(dg);
200     }
201 
202     void onClientMoved(void delegate(ClientInfo) dg)
203     {
204         clientMovedHandler = dg;
205     }
206 
207     void onClientMoved(void function(ClientInfo) dg)
208     {
209         import std.functional;
210         clientMovedHandler = toDelegate(dg);
211     }
212 
213     void send(T)(T value)
214     {
215         static struct MsgStruct {
216             string typename;
217             T value;
218         }
219         auto packetData = MsgStruct(T.stringof, value);
220         import std.exception : enforce;
221         enforce(server !is null, "Need to open connection first");
222         // serialize to json, then put into a packet.
223         auto datasize = serialize!(ReleaseOnWrite.no)(msg, packetData);
224         auto pkt = enet_packet_create(msg.window.ptr, datasize, ENET_PACKET_FLAG_RELIABLE);
225         if(!pkt)
226             throw new Exception("Could not create packet");
227         enet_peer_send(server, 0, pkt);
228     }
229 
230     // send and receive messages with a timeout for at least one message to arrive
231     bool process(Duration timeout = 0.seconds)
232     {
233         ENetEvent event;
234         bool result = false;
235         while(true)
236         {
237             auto res = enet_host_service(host, &event, cast(uint)timeout.total!"msecs");
238             // reset any timeout, next time we don't want to wait
239             timeout = 0.seconds;
240             if(res < 0)
241                 throw new Exception("Error in servicing host");
242             if(res == 0)
243                 break;
244             result = true;
245             final switch(event.type)
246             {
247             case ENET_EVENT_TYPE_NONE:
248                 // shouldn't happen
249                 break;
250             case ENET_EVENT_TYPE_CONNECT:
251                 // nothing to do
252                 break;
253             case ENET_EVENT_TYPE_RECEIVE:
254                 handlePacket(event);
255                 break;
256             case ENET_EVENT_TYPE_DISCONNECT:
257                 server = null;
258                 break;
259             }
260         }
261         return result;
262     }
263 
264     private void handlePacket(ref ENetEvent event)
265     {
266         import std.conv;
267         auto packetData = (cast(char*)event.packet.data)[0 .. event.packet.dataLength];
268         // deserialize the json
269         auto tokens = packetData.jsonTokenizer;
270         tokens.startCache();
271         size_t peerid = 0;
272         JSONItem item;
273         if(tokens.parseTo("id"))
274         {
275             item = tokens.next;
276             jsonExpect(item, JSONToken.Number, "Expected client id as a number");
277             peerid = item.data(packetData).to!size_t;
278         }
279         tokens.rewind();
280         enforce(tokens.parseTo("typename"), packetData);
281         item = tokens.next;
282         jsonExpect(item, JSONToken.String, "Expected type name");
283         auto converter = item.data(packetData) in handlers;
284         if(converter is null)
285         {
286             import std.stdio;
287             writeln("Cannot process message of type `", item.data(packetData), "`");
288         }
289         else
290         {
291             tokens.rewind();
292             tokens.endCache();
293             enforce(tokens.parseTo("value"));
294 
295             // set up to process the value
296             converter.process(tokens, peerid, converter.dg);
297         }
298     }
299 }