Serialization API, Server -> Clients messaging.

- Added zepha.serialize & zepha.deserialize. Said methods are automatically used when message-passing.
- Revised dump() to have better formatting and configurable spacing :)
master
Auri 2020-12-06 23:34:25 -08:00
parent 7618269434
commit 9466d7692a
15 changed files with 208 additions and 65 deletions

View File

@ -9,6 +9,7 @@ runfile(_PATH .. "modules/after")
runfile(_PATH .. "modules/vector")
runfile(_PATH .. "modules/entity")
runfile(_PATH .. "modules/callbacks")
runfile(_PATH .. "modules/serialization")
-- Register base models (if not on main menu)
if zepha.client or zepha.server then runfile(_PATH .. "game/init") end

View File

@ -1,56 +1,47 @@
-- Zepha Dump Function
-- Version 1.0
-- dumpTbl [private]
-- Recursive function to print the contents of a table.
local function dumpTbl(tbl, indent, scannedTables)
if not scannedTables then scannedTables = {} end
if not indent then indent = 0 end
--
-- Pretty-prints a lua value, properly displaying tables
-- with indentation, table collapsing, and more.
--
-- @param {any} val - The lua value to print.
-- @param {number | nil | false} nl - Controls the table expansion behavior. If left unspecified,
-- tables will expand if they contain more than 3 keys. If set to a number n,
-- tables will expand if they contain more than n keys, if set to false, tables will never expand.
-- @param {number | nil} idn - Internal value, do not assign manually.
--
scannedTables[tbl] = true
_G["format"] = function(val, nl, idn)
idn = idn or 0
if nl == nil then nl = 3 end
for k, v in pairs(tbl) do
if type(k) == "number" then
k = "[" .. k .. "]"
if type(val) == "function" or type(val) == "userdata" or type(val) == "thread" then return tostring(val) end
if type(val) == "string" then return '"' .. val .. '"' end
if type(val) == "nil" then return 'nil' end
if type(val) == "table" then
local strings = {}
local num_keys = 0
for _ in pairs(val) do num_keys = num_keys + 1 end
local use_nl = nl == true and true or (type(nl) == 'number' and num_keys >= nl)
local new_idn = use_nl and idn + 1 or idn
local delim = use_nl and '\n' or ' '
for k, v in pairs(val) do
table.insert(strings, format(k, nl, new_idn) .. ' = ' .. format(v, nl, new_idn))
end
local indentString = string.rep(" ", indent)
local formatting = indentString .. k .. " = "
local outer_idn = ''
if use_nl and idn then for i = 1, idn do outer_idn = outer_idn .. '\t' end end
local inner_idn = use_nl and outer_idn .. '\t' or ''
if type(v) == "function" then
v = "<function>"
elseif type(v) == "userdata" then
v = "<userdata>"
elseif type(v) == "string" then
v = '"' .. v .. '"'
elseif type(v) == "boolean" then
v = tostring(v)
end
if type(v) == "table" then
if scannedTables[v] ~= nil then
print(formatting .. "<circular>")
else
print(formatting .. "{")
dumpTbl(v, indent + 1, scannedTables)
print(indentString .. "}")
end
else
print(formatting .. v)
end
return '{' .. delim .. inner_idn .. table.concat(strings, ',' .. delim .. inner_idn) .. delim .. outer_idn .. '}'
end
return val
end
-- dump
-- Prints a human readable form of a value.
_G["dump"] = function(val)
if type(val) == "table" then
print("{")
dumpTbl(val, 1)
print("}")
return
end
if type(val) == "string" then print('"'..val..'"') return end
print(val)
return
_G["dump"] = function(val, nl)
print(format(val, nl))
end

View File

@ -0,0 +1,99 @@
zepha.serialize = function(data)
local data_type = type(data)
if data_type == 'table' then
local values = {}
for k, v in pairs(data) do
table.insert(values, zepha.serialize(k) .. ' = ' .. zepha.serialize(v))
end
return '{ ' .. table.concat(values, ', ') .. ' }'
end
if data_type == 'number' then return tostring(data) end
if data_type == 'string' then return '"' .. data:gsub('"', '\"') .. '"' end
if data_type == 'boolean' then return data and 'true' or 'false' end
if data_type ~= 'nil' then print('[ERR] Invalid serialization type: ' .. data_type) end
return 'nil'
end
local function trim(str)
return str:match('^%s*(.-)%s*$')
end
local function split(input, sep, op)
if sep == nil then sep = "%s" end
local t = {}
for str in string.gmatch(input, '([^'..sep..']+)') do
table.insert(t, op and op(str) or str) end
return t
end
local function find_match(str, a, b, off)
local pattern = '[' .. a .. b .. ']'
if not off then off = 1 end
local nest = 0
local found = str:find(pattern, off)
while found do
if str:sub(found, found) == a then nest = nest + 1
else nest = nest - 1 end
off = found + 1
if nest == 0 then return found
elseif nest < 0 then return nil end
found = str:find(pattern, off)
end
end
zepha.deserialize = function(str)
str = trim(str)
if str:sub(1, 1) == '{' then
local tbl = {}
str = trim(str:sub(2, #str - 1))
while #str > 0 do
local sep, key, val
if str:sub(1, 1) == ',' then
str = trim(str:sub(2))
end
if str:sub(1, 1) == '{' then
-- Handling a table key
local e = find_match(str, '{', '}')
local tbl_key = str:sub(1, e)
key = zepha.deserialize(tbl_key)
str = trim(str:sub(str:find('=', e + 1) + 1))
else
-- Handling a normal key
local end_ind = str:find('=')
key = zepha.deserialize(str:sub(1, end_ind - 1))
str = trim(str:sub(end_ind + 1))
end
if str:sub(1, 1) == '{' then
-- Handling a table value
local e = find_match(str, '{', '}')
local tbl_val = str:sub(1, e)
val = zepha.deserialize(tbl_val)
str = trim(str:sub(e + 1))
else
-- Handling a normal value
local end_ind = str:find(',')
val = zepha.deserialize(str:sub(1, (end_ind or #str + 1) - 1))
str = trim(str:sub(end_ind or #str + 1))
end
tbl[key] = val
end
return tbl
end
if str:find('^"') then return str:sub(2, #str - 1) end
if str:find('^true') then return true end
if str:find('^false') then return false end
if str:find('^nil') then return nil end
return tonumber(str)
end

View File

@ -66,17 +66,21 @@ void ClientNetworkInterpreter::receivedPacket(std::unique_ptr<PacketView> p) {
<< ". Is the server on a different protocol version?" << Log::endl;
break;
case Packet::Type::SERVER_INFO:serverSideChunkGens = p->d.read<unsigned int>();
case Packet::Type::SERVER_INFO:
serverSideChunkGens = p->d.read<unsigned int>();
break;
case Packet::Type::THIS_PLAYER_INFO:world.getPlayer()->handleAssertion(p->d);
case Packet::Type::THIS_PLAYER_INFO:
world.getPlayer()->handleAssertion(p->d);
break;
case Packet::Type::PLAYER_ENT_INFO:world.handlePlayerEntPacket(std::move(p));
case Packet::Type::PLAYER_ENT_INFO:
world.handlePlayerEntPacket(std::move(p));
break;
case Packet::Type::CHUNK:
case Packet::Type::MAPBLOCK:world.handleWorldPacket(std::move(p));
case Packet::Type::MAPBLOCK:
world.handleWorldPacket(std::move(p));
break;
case Packet::Type::BLOCK_SET: {
@ -86,13 +90,16 @@ void ClientNetworkInterpreter::receivedPacket(std::unique_ptr<PacketView> p) {
break;
}
case Packet::Type::ENTITY_INFO:world.getActiveDimension().l()->serverEntitiesInfo(p->d);
case Packet::Type::ENTITY_INFO:
world.getActiveDimension().l()->serverEntitiesInfo(p->d);
break;
case Packet::Type::ENTITY_REMOVED:world.getActiveDimension().l()->serverEntitiesRemoved(p->d);
case Packet::Type::ENTITY_REMOVED:
world.getActiveDimension().l()->serverEntitiesRemoved(p->d);
break;
case Packet::Type::INV_DATA:onInvPacket(std::move(p));
case Packet::Type::INV_DATA:
onInvPacket(std::move(p));
break;
case Packet::Type::INV_INVALID: {
@ -100,6 +107,12 @@ void ClientNetworkInterpreter::receivedPacket(std::unique_ptr<PacketView> p) {
std::string list = p->d.read<std::string>();
throw std::runtime_error("Invalid inventory " + source + ":" + list + " was request by client.");
}
case Packet::Type::MOD_MESSAGE: {
std::string channel = p->d.read<std::string>();
std::string message = p->d.read<std::string>();
world.handleModMessage(channel, message);
break;
}
}
}

View File

@ -13,15 +13,10 @@
#include "client/conn/ServerConnection.h"
class Model;
class Target;
class LocalWorld;
class PacketView;
class LocalPlayer;
class LocalSubgame;
enum class NetPlayerField;

View File

@ -7,6 +7,6 @@ void Api::Module::Message::bind() {
core.set_function("send_message", Util::bind_this(this, &Message::send_message));
}
void Api::Module::Message::send_message(const std::string& channel, const std::string& message) {
world.sendMessage(channel, message);
void Api::Module::Message::send_message(const std::string& channel, sol::object message) {
world.sendMessage(channel, core["serialize"](message));
}

View File

@ -12,7 +12,7 @@ namespace Api::Module {
void bind() override;
protected:
void send_message(const std::string& channel, const std::string& message);
void send_message(const std::string& channel, sol::object message);
};
}

View File

@ -162,7 +162,7 @@ void Server::playerPacketReceived(PacketView& p, PlayerPtr player) {
case Packet::Type::MOD_MESSAGE:
p.d.read<std::string>(source).read<std::string>(list);
game->getParser().safe_function(game->getParser().core["trigger"], "message",
source, list, Api::Usertype::ServerPlayer(player));
source, game->getParser().safe_function(game->getParser().core["deserialize"], list));
break;
}
}

View File

@ -71,6 +71,11 @@ void LocalWorld::handlePlayerEntPacket(std::unique_ptr<PacketView> p) {
// getActiveDimension().playerEntities.emplace_back(p->d.read<glm::vec3>(), id, playerModel);
}
void LocalWorld::handleModMessage(const std::string& channel, const std::string& message) {
game->getParser().safe_function(game->getParser().core["trigger"], "message",
channel, game->getParser().safe_function(game->getParser().core["deserialize"], message));
}
void LocalWorld::commitChunk(std::shared_ptr<Chunk> c) {
activeDimension->setChunk(std::move(c));
}

View File

@ -35,6 +35,8 @@ public:
void handlePlayerEntPacket(std::unique_ptr<PacketView> p);
void handleModMessage(const std::string& channel, const std::string& message);
void commitChunk(std::shared_ptr<Chunk> chunk);
virtual void sendMessage(const std::string& channel, const std::string& message) override;

View File

@ -240,5 +240,7 @@ void ServerWorld::sendChunksToPlayer(ServerPlayer& client) {
}
void ServerWorld::sendMessage(const std::string& channel, const std::string& message) {
std::cout << channel << ":" << message << std::endl;
auto p = Serializer().append(channel).append(message).packet(Packet::Type::MOD_MESSAGE);
for (auto& player : clients.players)
p.sendTo(player->getPeer(), Packet::Channel::ENTITY);
}

View File

@ -0,0 +1,7 @@
health.damage_player = function(player, damage)
local health = health.health_values[player.id]
health.health = health.health - damage
health.buffer = damage
zepha.send_message("@auri:health:damage", health)
end

View File

@ -0,0 +1,11 @@
health.health_values = {}
if zepha.server then
zepha.bind('player_join', function(player)
health.health_values[player.id] = { health = 20, buffer = 0 }
end)
else
zepha.bind('message', function(channel, message)
dump(message)
end)
end

View File

@ -1,7 +1,10 @@
local api = {}
_G['health'] = api
runfile(_PATH .. 'api')
runfile(_PATH .. 'core')
if zepha.client then
runfile(_PATH .. "interface")
runfile(_PATH .. 'interface')
api.default_render(true)
end

View File

@ -1,4 +1,4 @@
local friendly = false
local ATTACK_INTERVAL = 1
zepha.register_entity("zeus:default:rabbit", {
display = "model",
@ -21,8 +21,14 @@ zepha.register_entity("zeus:default:rabbit", {
self.object.anims:set_anim("run")
self.object.anims:play()
self.attack_timer = 0
end,
on_update = function(self, delta)
if self.attack_timer > 0 then
self.attack_timer = math.max(self.attack_timer - delta, 0)
end
local closest_player = nil
for _, player in pairs(zepha.players) do
if not closest_player or vector.dist(self.object.pos, player.pos)
@ -32,9 +38,13 @@ zepha.register_entity("zeus:default:rabbit", {
end
if not closest_player then return end
local offset = closest_player.pos - self.object.pos
local dist = offset:length()
if dist > 16 then return end
if dist > 16 then
self.attack_timer = ATTACK_INTERVAL
return
end
if dist > 0.5 then
local dir = offset:unit()
@ -54,7 +64,11 @@ zepha.register_entity("zeus:default:rabbit", {
3 * math.cos(math.rad(self.object.yaw + 90))
}
else
self.object.vel = V{}
self.object.vel = V {}
if self.attack_timer == 0 then
health.damage_player(closest_player, 1)
self.attack_timer = ATTACK_INTERVAL
end
end
end
})