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