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