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