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