SNIPPET TITLE: Invoking the server for data from the client, or informing the server for an action and waiting for a response as to whether that invocation was acceptable.
WHAT DOES IT DO?:
Do you, the client, want access to data that the server holds without using networked properties? Or maybe you want to check if it's acceptable for the client to do something? If so, this solves that.
BASE SNIPPET
Broadcast module:
-- error codes
local ERRORCODES = {
[1] = 'No response from server after packet [%s]';
[2] = 'Unresolved broadcast error after packet [%s]';
}
-- resolver
local resolver = { }
function resolver.new()
return setmetatable({ }, {
__index = resolver;
})
end
function resolver:reconcile(data)
if data.res and not data.res.error then
if self._handler then
self._handler(data)
end
else
if self._catch then
if data.res.error then
data.res.error = ERRORCODES[data.res.error]:format(data.id)
end
self._catch(data)
else
warn(ERRORCODES[2]:format(data.id))
end
end
end
function resolver:andThen(foo)
self._handler = foo
return self
end
function resolver:catch(foo)
self._catch = foo
return self
end
-- broadcaster
local broadcast = { }
function broadcast.new()
local this = { }
this.invocations = { }
-- private
local random = RandomStream.new()
local function send(event, data)
Task.Spawn(function ()
Events.BroadcastToServer(event, data)
end)
end
local function invoke(event, data)
data.id = this:getUUID()
local resolve = resolver.new()
this.invocations[data.id] = Events.Connect(data.id, function (data)
local connection = this.invocations[data.id]
this.invocations[data.id] = nil
coroutine.wrap(function (...)
resolve:reconcile(...)
end)(data)
return connection:Disconnect()
end)
send(event, data)
return resolve
end
local function construct()
return this
end
-- public
function this:getUUID()
-- modified from: https://gist.github.com/jrus/3197011
random:Mutate()
local template = 'xxxxxxxx-xxxx-4xxx-yxxx'
return string.gsub(template, '[xy]', function (c)
local v = (c == 'x') and random:GetInteger(0, 0xf) or random:GetInteger(8, 0xb)
return string.format('%x', v)
end)
end
function this:invokeServer(event, data)
return invoke(event, data)
end
function this:fireServer(event, data)
return send(event, data)
end
return construct()
end
return broadcast
Examples:
Server code example:
local handlers = {
-- you should handle these in another module, but for easy reading...
specialEvent = function (player, data)
if not somethingUnacceptable then
return 'specialEvent!'
end
end;
}
Events.ConnectForPlayer('registerAction', function (player, data)
local res
if data.class then
if handlers[data.class] then
res = handlers[data.class](data)
end
end
return Events.BroadcastToPlayer(player, data.id, {
id = data.id;
res = res or {
error = 1
}
})
end)
Client code example:
local broadcast = require('AssetIdForBroadcastModule').new()
local player = Game:GetLocalPlayer()
-- globals
local KEYS = {
LMB = 'ability_primary';
SPACE = 'ability_extra_17';
}
-- main
player.bindingPressedEvent:Connect(function (player, key)
if key == KEYS.LMB then
broadcast:invokeServer(
'registerAction',
{
class = 'specialEvent';
}
):andThen(function (data)
print('From server:', data.res)
-- do something relating to the 'specialEvent'
end):catch(function (data)
print(data.res.error)
end)
end
end)
ADDITIONAL INFO:
Admittedly, some of this is purely for syntactical sugar (i.e. :andThen and :catch probably didn't need to work like that but it looked nicer that way - waiting on someone to port a promise library).
I haven't handled rate limits and/or ability to queue in this but it could easily be added / I'll update it at some point.