Skip to content

Commit

Permalink
Implement mod communication channels (#6351)
Browse files Browse the repository at this point in the history
Implement network communication for channels

* Implement ModChannel manager server side to route incoming messages from clients to other clients
* Add signal handler switch on client & ModChannelMgr on client to handle channels
* Add Lua API bindings + client packet sending + unittests
* Implement server message sending
* Add callback from received message handler to Lua API using registration method
  • Loading branch information
nerzhul committed Sep 25, 2017
1 parent 6df312a commit 6f1c907
Show file tree
Hide file tree
Showing 37 changed files with 1,206 additions and 39 deletions.
2 changes: 2 additions & 0 deletions builtin/client/register.lua
Expand Up @@ -71,3 +71,5 @@ core.registered_on_dignode, core.register_on_dignode = make_registration()
core.registered_on_punchnode, core.register_on_punchnode = make_registration()
core.registered_on_placenode, core.register_on_placenode = make_registration()
core.registered_on_item_use, core.register_on_item_use = make_registration()
core.registered_on_modchannel_message, core.register_on_modchannel_message = make_registration()
core.registered_on_modchannel_signal, core.register_on_modchannel_signal = make_registration()
1 change: 1 addition & 0 deletions builtin/game/register.lua
Expand Up @@ -583,6 +583,7 @@ core.registered_on_punchplayers, core.register_on_punchplayer = make_registratio
core.registered_on_priv_grant, core.register_on_priv_grant = make_registration()
core.registered_on_priv_revoke, core.register_on_priv_revoke = make_registration()
core.registered_can_bypass_userlimit, core.register_can_bypass_userlimit = make_registration()
core.registered_on_modchannel_message, core.register_on_modchannel_message = make_registration()

--
-- Compatibility for on_mapgen_init()
Expand Down
3 changes: 3 additions & 0 deletions builtin/settingtypes.txt
Expand Up @@ -928,6 +928,9 @@ player_transfer_distance (Player transfer distance) int 0
# Whether to allow players to damage and kill each other.
enable_pvp (Player versus Player) bool true

# Enable mod channels support.
enable_mod_channels (Mod channels) bool false

# If this is set, players will always (re)spawn at the given position.
static_spawnpoint (Static spawnpoint) string

Expand Down
22 changes: 22 additions & 0 deletions clientmods/preview/init.lua
@@ -1,5 +1,6 @@
local modname = core.get_current_modname() or "??"
local modstorage = core.get_mod_storage()
local mod_channel

dofile("preview:example.lua")
-- This is an example function to ensure it's working properly, should be removed before merge
Expand All @@ -14,6 +15,21 @@ core.register_on_connect(function()
print("Server ip: " .. server_info.ip)
print("Server address: " .. server_info.address)
print("Server port: " .. server_info.port)

mod_channel = core.mod_channel_join("experimental_preview")
end)

core.register_on_modchannel_message(function(channel, sender, message)
print("[PREVIEW][modchannels] Received message `" .. message .. "` on channel `"
.. channel .. "` from sender `" .. sender .. "`")
core.after(1, function()
mod_channel:send_all("CSM preview received " .. message)
end)
end)

core.register_on_modchannel_signal(function(channel, signal)
print("[PREVIEW][modchannels] Received signal id `" .. signal .. "` on channel `"
.. channel)
end)

core.register_on_placenode(function(pointed_thing, node)
Expand Down Expand Up @@ -100,6 +116,12 @@ core.after(2, function()
preview_minimap()
end)

core.after(4, function()
if mod_channel:is_writeable() then
mod_channel:send_all("preview talk to experimental")
end
end)

core.after(5, function()
if core.ui.minimap then
core.ui.minimap:show()
Expand Down
32 changes: 32 additions & 0 deletions doc/client_lua_api.md
Expand Up @@ -683,6 +683,12 @@ Call these functions only at load time!
* Called when the local player uses an item.
* Newest functions are called first.
* If any function returns true, the item use is not sent to server.
* `minetest.register_on_modchannel_message(func(channel_name, sender, message))`
* Called when an incoming mod channel message is received
* You must have joined some channels before, and server must acknowledge the
join request.
* If message comes from a server mod, `sender` field is an empty string.

### Sounds
* `minetest.sound_play(spec, parameters)`: returns a handle
* `spec` is a `SimpleSoundSpec`
Expand Down Expand Up @@ -754,6 +760,16 @@ Call these functions only at load time!
* returns reference to mod private `StorageRef`
* must be called during mod load time

### Mod channels
![Mod channels communication scheme](docs/mod channels.png)

* `minetest.mod_channel_join(channel_name)`
* Client joins channel `channel_name`, and creates it, if necessary. You
should listen from incoming messages with `minetest.register_on_modchannel_message`
call to receive incoming messages. Warning, this function is asynchronous.
* You should use a minetest.register_on_connect(function() ... end) to perform
a successful channel join on client startup.

### Misc.
* `minetest.parse_json(string[, nullvalue])`: returns something
* Convert a string containing JSON data into the Lua equivalent
Expand Down Expand Up @@ -827,9 +843,25 @@ Call these functions only at load time!
Class reference
---------------

### ModChannel

An interface to use mod channels on client and server

#### Methods
* `leave()`: leave the mod channel.
* Client leaves channel `channel_name`.
* No more incoming or outgoing messages can be sent to this channel from client mods.
* This invalidate all future object usage
* Ensure your set mod_channel to nil after that to free Lua resources
* `is_writeable()`: returns true if channel is writeable and mod can send over it.
* `send_all(message)`: Send `message` though the mod channel.
* If mod channel is not writeable or invalid, message will be dropped.
* Message size is limited to 65535 characters by protocol.

### Minimap
An interface to manipulate minimap on client UI

#### Methods
* `show()`: shows the minimap (if not disabled by server)
* `hide()`: hides the minimap
* `set_pos(pos)`: sets the minimap position on screen
Expand Down
39 changes: 38 additions & 1 deletion doc/lua_api.txt
Expand Up @@ -1126,7 +1126,7 @@ The 2D perlin noise described by `noise_params` varies the Y co-ordinate of the
stratum midpoint. The 2D perlin noise described by `np_stratum_thickness`
varies the stratum's vertical thickness (in units of nodes). Due to being
continuous across mapchunk borders the stratum's vertical thickness is
unlimited.
unlimited.
`y_min` and `y_max` define the limits of the ore generation and for performance
reasons should be set as close together as possible but without clipping the
stratum's Y variation.
Expand Down Expand Up @@ -2496,6 +2496,20 @@ Call these functions only at load time!
* `minetest.register_can_bypass_userlimit(function(name, ip))`
* Called when `name` user connects with `ip`.
* Return `true` to by pass the player limit
* `minetest.register_on_modchannel_message(func(channel_name, sender, message))`
* Called when an incoming mod channel message is received
* You should have joined some channels to receive events.
* If message comes from a server mod, `sender` field is an empty string.
* `minetest.register_on_modchannel_signal(func(channel_name, signal))`
* Called when a valid incoming mod channel signal is received
* Signal id permit to react to server mod channel events
* Possible values are:
0: join_ok
1: join_failed
2: leave_ok
3: leave_failed
4: event_on_not_joined_channel
5: state_changed

### Other registration functions
* `minetest.register_chatcommand(cmd, chatcommand definition)`
Expand Down Expand Up @@ -2773,6 +2787,14 @@ and `minetest.auth_reload` call the authetification handler.
* spread these updates to neighbours and can cause a cascade
of nodes to fall.

### Mod channels
You can find mod channels communication scheme in `docs/mod_channels.png`.

* `minetest.mod_channel_join(channel_name)`
* Server joins channel `channel_name`, and creates it if necessary. You
should listen from incoming messages with `minetest.register_on_modchannel_message`
call to receive incoming messages

### Inventory
`minetest.get_inventory(location)`: returns an `InvRef`

Expand Down Expand Up @@ -3256,6 +3278,21 @@ These functions return the leftover itemstack.
Class reference
---------------

### ModChannel

An interface to use mod channels on client and server

#### Methods
* `leave()`: leave the mod channel.
* Server leaves channel `channel_name`.
* No more incoming or outgoing messages can be sent to this channel from server mods.
* This invalidate all future object usage
* Ensure your set mod_channel to nil after that to free Lua resources
* `is_writeable()`: returns true if channel is writeable and mod can send over it.
* `send_all(message)`: Send `message` though the mod channel.
* If mod channel is not writeable or invalid, message will be dropped.
* Message size is limited to 65535 characters by protocol.

### `MetaDataRef`
See `StorageRef`, `NodeMetaRef` and `ItemStackMetaRef`.

Expand Down
Binary file added doc/mod_channels.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions games/minimal/mods/experimental/init.lua
Expand Up @@ -2,6 +2,8 @@
-- Experimental things
--

dofile(minetest.get_modpath("experimental").."/modchannels.lua")

-- For testing random stuff

experimental = {}
Expand Down
16 changes: 16 additions & 0 deletions games/minimal/mods/experimental/modchannels.lua
@@ -0,0 +1,16 @@
--
-- Mod channels experimental handlers
--
local mod_channel = core.mod_channel_join("experimental_preview")

core.register_on_modchannel_message(function(channel, sender, message)
print("[minimal][modchannels] Server received message `" .. message
.. "` on channel `" .. channel .. "` from sender `" .. sender .. "`")

if mod_channel:is_writeable() then
mod_channel:send_all("experimental answers to preview")
mod_channel:leave()
end
end)

print("[minimal][modchannels] Code loaded!")
4 changes: 4 additions & 0 deletions minetest.conf.example
Expand Up @@ -1126,6 +1126,10 @@
# type: bool
# enable_pvp = true

# Enable mod channels.
# type: bool
# enable_mod_channels = false

# If this is set, players will always (re)spawn at the given position.
# type: string
# static_spawnpoint =
Expand Down
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Expand Up @@ -420,6 +420,7 @@ set(common_SRCS
mg_decoration.cpp
mg_ore.cpp
mg_schematic.cpp
modchannels.cpp
mods.cpp
nameidmapping.cpp
nodedef.cpp
Expand Down
58 changes: 57 additions & 1 deletion src/client.cpp
Expand Up @@ -39,6 +39,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "mapblock_mesh.h"
#include "mapblock.h"
#include "minimap.h"
#include "modchannels.h"
#include "mods.h"
#include "profiler.h"
#include "shader.h"
Expand Down Expand Up @@ -94,7 +95,8 @@ Client::Client(
m_chosen_auth_mech(AUTH_MECHANISM_NONE),
m_media_downloader(new ClientMediaDownloader()),
m_state(LC_Created),
m_game_ui_flags(game_ui_flags)
m_game_ui_flags(game_ui_flags),
m_modchannel_mgr(new ModChannelMgr())
{
// Add local player
m_env.setLocalPlayer(new LocalPlayer(this, playername));
Expand Down Expand Up @@ -1919,3 +1921,57 @@ std::string Client::getModStoragePath() const
{
return porting::path_user + DIR_DELIM + "client" + DIR_DELIM + "mod_storage";
}

/*
* Mod channels
*/

bool Client::joinModChannel(const std::string &channel)
{
if (m_modchannel_mgr->channelRegistered(channel))
return false;

NetworkPacket pkt(TOSERVER_MODCHANNEL_JOIN, 2 + channel.size());
pkt << channel;
Send(&pkt);

m_modchannel_mgr->joinChannel(channel, 0);
return true;
}

bool Client::leaveModChannel(const std::string &channel)
{
if (!m_modchannel_mgr->channelRegistered(channel))
return false;

NetworkPacket pkt(TOSERVER_MODCHANNEL_LEAVE, 2 + channel.size());
pkt << channel;
Send(&pkt);

m_modchannel_mgr->leaveChannel(channel, 0);
return true;
}

bool Client::sendModChannelMessage(const std::string &channel, const std::string &message)
{
if (!m_modchannel_mgr->canWriteOnChannel(channel))
return false;

if (message.size() > STRING_MAX_LEN) {
warningstream << "ModChannel message too long, dropping before sending "
<< " (" << message.size() << " > " << STRING_MAX_LEN << ", channel: "
<< channel << ")" << std::endl;
return false;
}

// @TODO: do some client rate limiting
NetworkPacket pkt(TOSERVER_MODCHANNEL_MSG, 2 + channel.size() + 2 + message.size());
pkt << channel << message;
Send(&pkt);
return true;
}

ModChannel* Client::getModChannel(const std::string &channel)
{
return m_modchannel_mgr->getModChannel(channel);
}
10 changes: 10 additions & 0 deletions src/client.h
Expand Up @@ -52,6 +52,7 @@ class IWritableNodeDefManager;
//class IWritableCraftDefManager;
class ClientMediaDownloader;
struct MapDrawControl;
class ModChannelMgr;
class MtEventManager;
struct PointedThing;
class MapDatabase;
Expand Down Expand Up @@ -224,6 +225,8 @@ class Client : public con::PeerHandler, public InventoryManager, public IGameDef
void handleCommand_LocalPlayerAnimations(NetworkPacket* pkt);
void handleCommand_EyeOffset(NetworkPacket* pkt);
void handleCommand_UpdatePlayerList(NetworkPacket* pkt);
void handleCommand_ModChannelMsg(NetworkPacket *pkt);
void handleCommand_ModChannelSignal(NetworkPacket *pkt);
void handleCommand_SrpBytesSandB(NetworkPacket* pkt);
void handleCommand_CSMFlavourLimits(NetworkPacket *pkt);

Expand Down Expand Up @@ -424,6 +427,11 @@ class Client : public con::PeerHandler, public InventoryManager, public IGameDef
return m_csm_noderange_limit;
}

bool joinModChannel(const std::string &channel);
bool leaveModChannel(const std::string &channel);
bool sendModChannelMessage(const std::string &channel, const std::string &message);
ModChannel *getModChannel(const std::string &channel);

private:

// Virtual methods from con::PeerHandler
Expand Down Expand Up @@ -580,4 +588,6 @@ class Client : public con::PeerHandler, public InventoryManager, public IGameDef
// CSM flavour limits byteflag
u64 m_csm_flavour_limits = CSMFlavourLimit::CSM_FL_NONE;
u32 m_csm_noderange_limit = 8;

std::unique_ptr<ModChannelMgr> m_modchannel_mgr;
};
1 change: 1 addition & 0 deletions src/defaultsettings.cpp
Expand Up @@ -300,6 +300,7 @@ void set_default_settings(Settings *settings)
settings->setDefault("default_password", "");
settings->setDefault("default_privs", "interact, shout");
settings->setDefault("enable_pvp", "true");
settings->setDefault("enable_mod_channels", "false");
settings->setDefault("disallow_empty_password", "false");
settings->setDefault("disable_anticheat", "false");
settings->setDefault("enable_rollback_recording", "false");
Expand Down
7 changes: 7 additions & 0 deletions src/gamedef.h
Expand Up @@ -33,6 +33,7 @@ class MtEventManager;
class IRollbackManager;
class EmergeManager;
class Camera;
class ModChannel;
class ModMetadata;

namespace irr { namespace scene {
Expand Down Expand Up @@ -78,4 +79,10 @@ class IGameDef
virtual std::string getModStoragePath() const = 0;
virtual bool registerModStorage(ModMetadata *storage) = 0;
virtual void unregisterModStorage(const std::string &name) = 0;

virtual bool joinModChannel(const std::string &channel) = 0;
virtual bool leaveModChannel(const std::string &channel) = 0;
virtual bool sendModChannelMessage(const std::string &channel,
const std::string &message) = 0;
virtual ModChannel *getModChannel(const std::string &channel) = 0;
};

0 comments on commit 6f1c907

Please sign in to comment.