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