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