· 8 years ago · Aug 15, 2018, 06:08 PM
1-- Libraries
2local ffi = assert(require "ffi", "FFI library required")
3local cenet = assert(require "enet", "Enet library required")
4
5local host_metatable = debug.getregistry()["enet_host"].__index
6local peer_metatable = debug.getregistry()["enet_peer"].__index
7
8-- Variables
9local cdefs = {} -- Temporary table for storing the components of the cdef
10local packets_by_id = {} -- ID: p_packet_struct
11local packets_by_struct = {} -- p_packet_struct: ID
12local compiled = false -- Whether cdefs has been used
13local packet_size -- Size of largest packet struct (and the size of the packet untion)
14local packet_pointer -- Pointer to location in memory packets are moved to before being cast
15local print_unhandled_packets = true
16local struct_checksum
17
18-- helper functions
19
20-- The stock assert function evaluates the error message even if condition is false
21local function assertx(condition, ...)
22 if not condition then error(table.concat({...})) else return condition end
23end
24
25-- Adds a formatted string to the cdef table
26local function add_cdef(format_string, ...)
27 assertx(not compiled, "cdefs have already been compiled")
28 cdefs[#cdefs+1] = string.format(format_string, ...)
29end
30
31local function printf(format_string, ...)
32 print(string.format("[%s] - " .. format_string, cenet.server and"SERVER"or cenet.client and"CLIENT"or"UNKNOWN", ...))
33end
34
35-- Used as a default for callbacks that do not exist yet
36local anonymous
37if print_unhandled_packets then
38 anonymous = function(packet, peer)
39 print(string.format("Got unhandled packet '%s' (ID: %d) from peer %d",
40 packets_by_id[packet.packet_id], packet.packet_id, peer:connect_id()))
41 end
42else
43 anonymous = function() end
44end
45
46-- Constants, used to make array indexing a bit more readable in the following function
47local TYPE = 1
48local NAME = 2
49local SIZE = 3
50
51function cenet.register_packet(struct_name, struct_data, flag)
52 assertx(struct_name, "Missing struct name")
53 assertx(struct_data, "Missing struct data")
54 assertx(flag == nil or flag == "unsequenced" or flag == "reliable" or flag == "reliable", "Illegal flag '", flag ,"'")
55 assertx(not packets_by_struct[struct_name], "Struct '", struct_name, "' already exists.")
56 assertx(struct_name:find("^[a-zA-Z_][a-zA-Z0-9_]*$"), "Illegal struct name '", struct_name, "'")
57
58 add_cdef("typedef struct {")
59 add_cdef("\tint packet_id;")
60
61 for type_num, data in ipairs(struct_data) do
62 assertx(data[NAME] ~= "packet_id", "Packet cannot have field 'packet_id'")
63 assertx(data[TYPE], "Missing type identifier for type '", type_num, "' of struct '", struct_name, "'")
64
65 if data[SIZE] then
66 assertx(tonumber(data[SIZE]) and tonumber(data[SIZE]) > 0,
67 "Third argument for struct member must be positive number (size)")
68 data[SIZE] = "[" .. data[SIZE] .. "]"
69 else
70 data[SIZE] = ""
71 end
72
73 add_cdef("\t%s %s%s;", data[TYPE], data[NAME], data[SIZE])
74 end
75
76 add_cdef("} %s; \n", struct_name)
77
78 packets_by_id[#packets_by_id+1] = {
79 struct = struct_name,
80 callback = anonymous,
81 flag = flag
82 }
83 packets_by_struct[struct_name] = #packets_by_id
84 return #packets_by_id
85end
86
87function cenet.register_callback(packet_id, callback)
88 assertx(packets_by_id[packet_id].struct, "No such struct")
89 packets_by_id[packet_id].callback = callback or anonymous
90end
91
92function cenet.call_callback(packet_id, ...)
93 return packets_by_id[packet_id].callback(...)
94end
95
96function cenet.compile()
97 add_cdef("typedef union {") -- Union containing each packet
98 add_cdef("\tint packet_id;")
99 for _, packet in pairs(packets_by_id) do
100 add_cdef("\t%s %s;", packet.struct, packet.struct)
101 end
102
103 add_cdef("} packet;\n")
104
105 -- External C functions
106 add_cdef("void *malloc(size_t size);")
107
108 -- Make checksum of packets
109 struct_checksum = love.data.hash("md5", table.concat(cdefs))
110 printf("Checksum: %s", love.data.encode("string", "base64", struct_checksum))
111
112 -- Finally, "compile" the defitions
113 ffi.cdef(table.concat(cdefs, "\n"))
114
115 packet_size = ffi.sizeof("packet")
116 packet_pointer = ffi.C.malloc(packet_size) -- Make enough room for the biggest packet possible
117end
118
119-- Get enums
120function cenet.get_packet_enums() return packets_by_struct end
121
122-- Callbacks for connection and disconnection events
123function cenet.on_connect(event) end --luacheck: ignore
124function cenet.on_disconnect(event) end --luacheck: ignore
125
126-- Monkeypatching key parts of enet
127local enet_host_create = cenet.host_create
128function cenet.host_create(address, peer_count, channel_count, download, upload)
129 local host = enet_host_create(address, peer_count, channel_count, download, upload)
130 if address then
131 cenet.server = {host = host}
132 else
133 cenet.client = {host = host}
134 end
135 return host
136end
137
138local host_connect = host_metatable.connect
139function host_metatable:connect(address, channel_count, data)
140 local server = host_connect(self, address, channel_count, data)
141 cenet.client.server = server
142 return server
143end
144
145local host_service = host_metatable.service
146function host_metatable:service(timeout)
147 timeout = timeout or 0
148 local event = host_service(self, timeout)
149 while event do
150 if event.type == "receive" then
151 ffi.copy(packet_pointer, event.data)
152 local packet = ffi.cast("packet *", packet_pointer) -- TODO: remove concatenation
153 local packet_meta = packets_by_id[packet.packet_id]
154 packet = ffi.cast(packet_meta.struct .. " *", packet_pointer)
155
156 printf("got packet: %s", packets_by_id[packet.packet_id].struct)
157 packet_meta.callback(packet, event.peer, self)
158 elseif event.type == "connect" then
159 cenet.on_connect(event)
160 elseif event.type == "disconnect" then
161 cenet.on_disconnect(event)
162 end
163 event = host_service(self)
164 end
165end
166
167local peer_send = peer_metatable.send
168function peer_metatable:send(packet_id, ...)
169 local packet_meta = packets_by_id[packet_id]
170 peer_send(self, ffi.string(ffi.new(packet_meta.struct, packet_id, ...), ffi.sizeof(packet_meta.struct)),
171 0, packet_meta.flag)
172 printf("sent packet: %s", packet_meta.struct)
173end
174
175function peer_metatable:send_raw(packet)
176 peer_send(self, ffi.string(packet, ffi.sizeof(packets_by_id[packet.packet_id])))
177end
178
179local host_broadcast = host_metatable.broadcast
180function host_metatable:broadcast(packet_id, ...)
181 local packet_meta = packets_by_id[packet_id]
182 host_broadcast(self, ffi.string(ffi.new(packet_meta.struct, packet_id, ...), ffi.sizeof(packet_meta.struct)),
183 packet_meta.channel, packet_meta.flag)
184 printf("broadcasted packet: %s", packet_meta.struct)
185end
186
187function host_metatable:broadcast(packet)
188 host_broadcast(self, ffi.string(packet, ffi.sizeof(packets_by_id[packet.packet_id])))
189end
190
191-- Networked class implementation
192
193local net_classes_by_id = {}
194local net_classes_by_name = {}
195
196local entities = {}
197
198-- Relevant packets
199
200-- Packet sent froms server to all clients informing them of new entity
201-- with entity ID and entity owner.
202cenet.register_packet("p_entity_created", { -- EXAMPLE: DEFINED AUTOMAGICALLY
203 {"unsigned short", "entity_id"},
204 {"unsigned long", "owner"},
205 {"unsigned short", "entity_class_id"}
206}, "reliable")
207
208cenet.register_callback(packets_by_struct.p_entity_created,
209function(p_entity_created)
210 local instance = net_classes_by_id[p_entity_created.entity_class_id]:__instanciate()
211 entities[p_entity_created.entity_id] = instance
212 instance.__owner = p_entity_created.owner
213 instance.__entity_id = p_entity_created.entity_id
214 cenet.new_entity(instance)
215 printf("broadcasted packet: %s", cenet.server and "SERVER" or "CLIENT")
216 return instance
217end)
218
219-- Packet sent to server to request new entity
220cenet.register_packet("p_request_entity", {
221 {"unsigned short", "entity_class_id"},
222}, "reliable")
223
224cenet.register_callback(packets_by_struct.p_request_entity,
225function(p_request_entity, peer)
226 assertx(cenet.server, "Cannot create entity from client")
227 local instance = net_classes_by_id[p_request_entity.entity_class_id]:__instanciate()
228 instance.__owner = peer:connect_id()
229
230 local found_id = false
231 for i = 1, #entities do
232 if not entities[i].alive then
233 entities[i] = instance
234 instance.__entity_id = i
235 found_id = true break
236 end
237 end
238 if not found_id then
239 entities[#entities+1] = instance
240 instance.__entity_id = #entities
241 end
242
243 cenet.server.host:broadcast(packets_by_struct.p_entity_created, instance.__entity_id, instance.__owner,
244 instance.__entity_class_id)
245end)
246
247
248local unpack = unpack or table.unpack
249
250--TEMP
251function cenet.new_entity(entity) end
252function cenet.temp_get_entities() return entities end
253
254function cenet.networked_class(name, flush_data)
255 assertx(not compiled, "Cannot create new networked class after cdefs are compiled")
256 assertx(not net_classes_by_name[name], "Networked class '", name, "' already defined.")
257
258 table.insert(flush_data, 1, {"unsigned short", "entity_id"})
259
260 local net_class = setmetatable({
261 __entity_class_id = #net_classes_by_id + 1,
262 __instanciate = function(self)
263 local instance = setmetatable({alive=true}, self)
264 instance:populate()
265
266 return instance
267 end,
268 __flush_data = flush_data,
269 __flush_struct_id = cenet.register_packet("nc_flush_" .. name, flush_data),
270 __flush = function(self)
271 local new_flush_data = {}
272 for i = 2, #self.__flush_data do -- TODO: create temp table as to not have to iterate from 2
273 new_flush_data[i-1] = self[self.__flush_data[NAME]]
274 end
275 cenet.client.server:send(self.__flush_struct_id, self.__entity_id, unpack(new_flush_data))
276 end
277 }, {
278 __call = function(self, ...) -- TODO: create class instance from server
279 assertx(cenet.client, "class creation request must be run from client")
280
281 cenet.client.server:send(packets_by_struct.p_request_entity, self.__entity_class_id)
282 self:__flush()
283 end
284 })
285 net_class.__index = net_class
286
287 cenet.register_callback(net_class.__flush_struct_id,
288 function(flush_struct)
289 if cenet.server then
290 cenet.server.host:broadcast_raw(flush_struct)
291 end
292
293 local instance = entities[flush_struct.entity_id]
294
295 for _ = 2, #instance.__flush_data do
296 instance[instance.__flush_data[NAME]] = flush_struct[instance.__flush_data[NAME]]
297 end
298 end)
299
300 net_classes_by_id[#net_classes_by_id+1] = net_class
301 net_classes_by_name[name] = #net_classes_by_id
302
303 return net_class
304end
305
306return cenet