1 module server;
2 import gameserver.common;
3 
4 import enet.enet;
5 import iopipe.json.serialize;
6 import iopipe.json.parser;
7 import iopipe.bufpipe;
8 import std.range;
9 
10 auto addrStr(ENetAddress addr)
11 {
12     static struct Printer
13     {
14         ENetAddress addr;
15         void toString(Out)(Out output)
16         {
17             import std.format : formattedWrite;
18             foreach(i; 0 .. 4)
19             {
20                 auto octet = (addr.host >> (8 * i)) & 0xff;
21                 formattedWrite(output, "%s%d", i > 0 ? "." : "", octet);
22             }
23             formattedWrite(output, ":%d", addr.port);
24         }
25     }
26     return Printer(addr);
27 }
28 
29 struct Server
30 {
31     private {
32         struct Room
33         {
34             int id;
35             ENetPeer*[] players;
36             bool removePlayer(size_t id)
37             {
38                 import std.algorithm : remove, SwapStrategy;
39                 auto origLen = players.length;
40                 players = players.remove!(v => cast(size_t)v.data == id, SwapStrategy.stable);
41                 players.assumeSafeAppend;
42                 assert(players.length != origLen);
43                 // return true if this room can be removed. Room id 0 must
44                 // always be present.
45                 return this.id != 0 && players.length == 0;
46             }
47         }
48 
49         ENetHost *host;
50         size_t clientId;
51         int roomId;
52         ClientInfo[] players;
53         Room[] rooms = [Room(0, null)];
54         BufferPipe msg; // TODO: instead of this, use an iopipe buffer with malloc.
55     }
56 
57     void initialize(ushort port) {
58         ENetAddress *addr = new ENetAddress;
59         addr.host = ENET_HOST_ANY;
60         addr.port = port;
61         this.host = enet_host_create(addr, 10, 2, 0, 0);
62     }
63 
64     // send and receive messages
65     void process(uint timeout)
66     {
67         ENetEvent event;
68         auto res = enet_host_service(host, &event, timeout);
69         if(res < 0)
70             throw new Exception("Error in servicing host");
71         final switch(event.type)
72         {
73         case ENET_EVENT_TYPE_NONE:
74             break;
75         case ENET_EVENT_TYPE_CONNECT:
76             handleConnect(event);
77             break;
78         case ENET_EVENT_TYPE_RECEIVE:
79             handlePacket(event);
80             break;
81         case ENET_EVENT_TYPE_DISCONNECT:
82             handleDisconnect(event);
83             break;
84         }
85     }
86 
87     private void handlePacket(ref ENetEvent event)
88     {
89         // rebroadcast the data to the clients, but of course, put in the client id
90         import std.conv : toChars;
91         import std.stdio;
92         auto eventmsg = (cast(const char *)event.packet.data)[0 .. event.packet.dataLength];
93         // if the message is one we recognize, then handle it, otherwise, send
94         // it to the related peers
95 
96         auto client = getClient(event); 
97         assert(client);
98         auto tokens = eventmsg.jsonTokenizer!(false);
99         tokens.startCache();
100         if(!tokens.parseTo("typename"))
101         {
102             writeln("Ignoring malformed packet: ", eventmsg);
103             return; // ignore this, no idea how to handle it
104         }
105         scope(failure) writeln("Could not parse packet :", eventmsg);
106         auto item = tokens.next;
107         jsonExpect(item, JSONToken.String, "Expected type name");
108         auto typename = item.data(eventmsg);
109         if(typename == "MoveToRoom")
110         {
111             tokens.rewind();
112             tokens.endCache();
113             if(!tokens.parseTo("value"))
114             {
115                 // ignore
116                 writeln("Ignoring malformed packet: ", eventmsg);
117                 return;
118             }
119             auto mtr = deserialize!MoveToRoom(tokens);
120             writeln("processing move to room, ", mtr);
121             if(mtr.id == -1)
122             {
123                 rooms.length = rooms.length + 1;
124                 rooms[$-1].id = ++roomId;
125                 mtr.id = roomId;
126                 writeln("Adding room id ", roomId);
127             }
128 
129             auto room = getRoom(mtr.id);
130             if(!room)
131             {
132                 // ignore
133                 writeln("Ignoring incorrect move to room, ", eventmsg);
134                 return;
135             }
136             auto curRoom = getRoom(client.room_id);
137             if(curRoom)
138                 if(curRoom.removePlayer(client.id))
139                     removeRoom(curRoom);
140             room.players ~= event.peer;
141             client.room_id = room.id;
142             // send out a message to all players about the move
143             auto packet = makePacket(ClientInfoChanged(*client));
144             enet_host_broadcast(host, 0, packet);
145         }
146         else
147         {
148             // send the message to all peers in the same room
149             enum header =`{"id": `;
150             auto idr = toChars(client.id);
151             ENetPacket *packet = enet_packet_create(null,
152                 eventmsg.length + // message
153                 idr.length + // id
154                 header.length, // header length (substitute { for ,)
155                 ENET_PACKET_FLAG_RELIABLE);
156             auto msg = (cast(char *)packet.data)[0 .. packet.dataLength];
157             // write the jsonmsg, while also putting in the id.
158             put(msg, header);
159             put(msg, idr);
160             put(msg, ',');
161             put(msg, eventmsg[1 .. $]); // skip opening brace
162             assert(msg.length == 0);
163             // send to all the clients in the room
164             auto room = getRoom(client.room_id);
165             //assert(room);
166             if(!room) // this shouldn't happen, but don't want to crash.
167             {
168                 stderr.writeln("Client id ", client.id, " Is in invalid room id ", client.room_id);
169                 return;
170             }
171             foreach(p; room.players)
172             {
173                 enet_peer_send(p, 0, packet);
174             }
175         }
176     }
177 
178     private ENetPacket *makePacket(T)(T value)
179     {
180         static struct MsgStruct {
181             string typename;
182             T value;
183         }
184 
185         auto packetData = MsgStruct(T.stringof, value);
186         auto datasize = serialize!(ReleaseOnWrite.no)(msg, packetData);
187         auto pkt = enet_packet_create(msg.window.ptr, datasize, ENET_PACKET_FLAG_RELIABLE);
188         if(!pkt)
189             throw new Exception("Could not create packet");
190         return pkt;
191     }
192 
193     private void handleConnect(ref ENetEvent event)
194     {
195         // send the peer a message telling him his client id
196         import std.format : format;
197         ++clientId;
198         import std.stdio;
199         writeln("Client connected from ", event.peer.address.addrStr, " assigning id ", clientId);
200         auto newClient = ClientInfo(clientId, 0);
201         auto hello = Hello(ClientInfo(clientId, 0), players);
202         ENetPacket *packet = makePacket(hello);
203         event.peer.data = cast(void *)clientId;
204         enet_peer_send(event.peer, 0, packet);
205         players ~= newClient;
206         rooms[0].players ~= event.peer;
207 
208         // tell all clients that a client was added
209         auto clientAdded = ClientAdded(newClient);
210         packet = makePacket(clientAdded);
211         enet_host_broadcast(host, 0, packet);
212     }
213 
214     private ClientInfo *getClient(ref ENetEvent event)
215     {
216         return getClient(cast(size_t)event.peer.data);
217     }
218 
219     private ClientInfo *getClient(size_t id)
220     {
221         import std.algorithm : find;
222         import std.format : format;
223         auto searched = players.find!((ref p, x) => p.id == x)(id);
224         if(searched.empty())
225             return null;
226         return &searched.front();
227     }
228 
229     private Room *getRoom(int id)
230     {
231         import std.algorithm : find;
232         import std.format : format;
233         auto searched = rooms.find!((ref p, int x) => p.id == x)(id);
234         if(searched.empty())
235             return null;
236         return &searched.front();
237     }
238 
239     private void handleDisconnect(ref ENetEvent event)
240     {
241         // tell clients that the client disconnected
242         import std.format : format;
243         auto client = *getClient(event);
244         import std.stdio;
245         writeln("Client disconnected from address ", event.peer.address.addrStr, " with id ", client.id);
246         auto clientRemoved = ClientRemoved(client.id);
247         auto packet = makePacket(clientRemoved);
248         enet_host_broadcast(host, 0, packet);
249         // ensure the peer is removed from the list and from its room
250         import std.algorithm : remove, SwapStrategy;
251         auto origLen = players.length;
252         players = players.remove!(v => v.id == client.id, SwapStrategy.stable);
253         assert(players.length != origLen);
254         players.assumeSafeAppend;
255         auto room = getRoom(client.room_id);
256         assert(room);
257         if(room.removePlayer(client.id))
258             removeRoom(room);
259         // do this last. Because we use it to identify peers.
260         event.peer.data = null;
261     }
262 
263     private void removeRoom(Room *ptr)
264     {
265         assert(ptr.players.length == 0);
266         import std.stdio;
267         writeln("Removing room id ", ptr.id);
268         size_t idx = ptr - rooms.ptr;
269         assert(idx != 0 && idx < rooms.length);
270         rooms[idx] = rooms[$-1];
271         rooms.length = rooms.length - 1;
272         rooms.assumeSafeAppend;
273     }
274 }
275