1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2025-12-23 15:52:55 +01:00

Start implementing TCP API

This commit is contained in:
Ted John
2020-08-17 03:53:37 +01:00
parent 359bbee9f5
commit dce547af99
9 changed files with 449 additions and 1 deletions

View File

@@ -1221,6 +1221,9 @@ declare global {
kickPlayer(index: number): void;
sendMessage(message: string): void;
sendMessage(message: string, players: number[]): void;
createServer(): SocketServer;
createSocket(): Socket;
}
type NetworkMode = "none" | "server" | "client";
@@ -1677,4 +1680,30 @@ declare global {
moveTo(position: CoordsXY | CoordsXYZ): void;
scrollTo(position: CoordsXY | CoordsXYZ): void;
}
/**
* Represents a server that can listen for incomming connections.
* Based on node.js net.Server, see https://nodejs.org/api/net.html for more information.
*/
interface SocketServer {
listen(port: number): SocketServer;
close(): SocketServer;
on(event: 'connection', callback: (socket: Socket) => void): SocketServer;
}
/**
* Represents a socket such as a TCP connection.
* Based on node.js net.Socket, see https://nodejs.org/api/net.html for more information.
*/
interface Socket {
connect(port: number, host: string, callback: Function): Socket;
destroy(error: object): Socket;
setNoDelay(noDelay: boolean): Socket;
end(data?: string): Socket;
write(data: string): boolean;
on(event: 'data', callback: (data: string) => void): Socket;
on(event: 'close', callback: (hadError: boolean) => void): Socket;
}
}

View File

@@ -410,6 +410,7 @@
<ClInclude Include="scripting\ScPark.hpp" />
<ClInclude Include="scripting\ScRide.hpp" />
<ClInclude Include="scripting\ScriptEngine.h" />
<ClInclude Include="scripting\ScSocketServer.hpp" />
<ClInclude Include="scripting\ScTile.hpp" />
<ClInclude Include="sprites.h" />
<ClInclude Include="title\TitleScreen.h" />

View File

@@ -34,6 +34,9 @@
#ifndef SHUT_RD
#define SHUT_RD SD_RECEIVE
#endif
#ifndef SHUT_WR
#define SHUT_WR SD_SEND
#endif
#ifndef SHUT_RDWR
#define SHUT_RDWR SD_BOTH
#endif
@@ -464,6 +467,14 @@ public:
thread.detach();
}
void Finish() override
{
if (_status == SOCKET_STATUS_CONNECTED)
{
shutdown(_socket, SHUT_WR);
}
}
void Disconnect() override
{
if (_status == SOCKET_STATUS_CONNECTED)
@@ -844,6 +855,7 @@ void DisposeWSA()
std::unique_ptr<ITcpSocket> CreateTcpSocket()
{
InitialiseWSA();
return std::make_unique<TcpSocket>();
}

View File

@@ -67,6 +67,7 @@ public:
virtual size_t SendData(const void* buffer, size_t size) abstract;
virtual NetworkReadPacket ReceiveData(void* buffer, size_t size, size_t* sizeReceived) abstract;
virtual void Finish() abstract;
virtual void Disconnect() abstract;
virtual void Close() abstract;
};

View File

@@ -257,6 +257,12 @@ namespace OpenRCT2::Scripting
return DukValue::take_from_stack(ctx);
}
template<> inline DukValue ToDuk(duk_context* ctx, const bool& value)
{
duk_push_boolean(ctx, value);
return DukValue::take_from_stack(ctx);
}
template<> inline DukValue ToDuk(duk_context* ctx, const int32_t& value)
{
duk_push_int(ctx, value);
@@ -269,6 +275,11 @@ namespace OpenRCT2::Scripting
return DukValue::take_from_stack(ctx);
}
template<> inline DukValue ToDuk(duk_context* ctx, const std::string& value)
{
return ToDuk(ctx, std::string_view(value));
}
template<size_t TLen> inline DukValue ToDuk(duk_context* ctx, const char (&value)[TLen])
{
duk_push_string(ctx, value);

View File

@@ -11,12 +11,14 @@
#ifdef ENABLE_SCRIPTING
# include "../Context.h"
# include "../actions/NetworkModifyGroupAction.hpp"
# include "../actions/PlayerKickAction.hpp"
# include "../actions/PlayerSetGroupAction.hpp"
# include "../network/NetworkAction.h"
# include "../network/network.h"
# include "Duktape.hpp"
# include "ScSocketServer.hpp"
namespace OpenRCT2::Scripting
{
@@ -447,6 +449,32 @@ namespace OpenRCT2::Scripting
# endif
}
std::shared_ptr<ScSocketServer> createServer()
{
# ifndef DISABLE_NETWORK
auto& scriptEngine = GetContext()->GetScriptEngine();
auto plugin = scriptEngine.GetExecInfo().GetCurrentPlugin();
auto socket = std::make_shared<ScSocketServer>(plugin);
scriptEngine.AddSocket(socket);
return socket;
# else
duk_error(_context, DUK_ERR_ERROR, "Networking has been disabled.");
# endif
}
std::shared_ptr<ScSocket> createSocket()
{
# ifndef DISABLE_NETWORK
auto& scriptEngine = GetContext()->GetScriptEngine();
auto plugin = scriptEngine.GetExecInfo().GetCurrentPlugin();
auto socket = std::make_shared<ScSocket>(plugin);
scriptEngine.AddSocket(socket);
return socket;
# else
duk_error(_context, DUK_ERR_ERROR, "Networking has been disabled.");
# endif
}
static void Register(duk_context* ctx)
{
dukglue_register_property(ctx, &ScNetwork::mode_get, nullptr, "mode");
@@ -462,6 +490,9 @@ namespace OpenRCT2::Scripting
dukglue_register_method(ctx, &ScNetwork::getPlayer, "getPlayer");
dukglue_register_method(ctx, &ScNetwork::kickPlayer, "kickPlayer");
dukglue_register_method(ctx, &ScNetwork::sendMessage, "sendMessage");
dukglue_register_method(ctx, &ScNetwork::createServer, "createServer");
dukglue_register_method(ctx, &ScNetwork::createSocket, "createSocket");
}
};
} // namespace OpenRCT2::Scripting

View File

@@ -0,0 +1,313 @@
/*****************************************************************************
* Copyright (c) 2014-2020 OpenRCT2 developers
*
* For a complete list of all authors, please refer to contributors.md
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is licensed under the GNU General Public License version 3.
*****************************************************************************/
#pragma once
#ifdef ENABLE_SCRIPTING
# include "../Context.h"
# include "../network/Socket.h"
# include "Duktape.hpp"
# include "ScriptEngine.h"
namespace OpenRCT2::Scripting
{
class ScSocketBase
{
private:
std::shared_ptr<Plugin> _plugin;
public:
ScSocketBase(const std::shared_ptr<Plugin>& plugin)
: _plugin(plugin)
{
}
virtual ~ScSocketBase()
{
Dispose();
}
const std::shared_ptr<Plugin>& GetPlugin() const
{
return _plugin;
}
virtual void Update() = 0;
virtual void Dispose()
{
}
virtual bool IsDisposed() const = 0;
};
class ScSocket : public ScSocketBase
{
private:
std::unique_ptr<ITcpSocket> _socket;
bool _disposed{};
DukValue _onClose;
DukValue _onData;
public:
ScSocket(const std::shared_ptr<Plugin>& plugin)
: ScSocketBase(plugin)
{
}
ScSocket(const std::shared_ptr<Plugin>& plugin, std::unique_ptr<ITcpSocket>&& socket)
: ScSocketBase(plugin)
, _socket(std::move(socket))
{
}
private:
ScSocket* destroy(const DukValue& error)
{
if (_socket != nullptr)
{
_socket->Close();
_socket = nullptr;
}
return this;
}
ScSocket* end(const DukValue& data)
{
if (_disposed)
{
auto ctx = GetContext()->GetScriptEngine().GetContext();
duk_error(ctx, DUK_ERR_ERROR, "Socket is disposed.");
}
else if (_socket != nullptr)
{
if (data.type() == DukValue::Type::STRING)
{
write(data.as_string());
}
_socket->Finish();
}
return this;
}
bool write(const std::string& data)
{
if (_disposed)
{
auto ctx = GetContext()->GetScriptEngine().GetContext();
duk_error(ctx, DUK_ERR_ERROR, "Socket is disposed.");
}
else if (_socket != nullptr)
{
_socket->SendData(data.c_str(), data.size());
return true;
}
return false;
}
ScSocket* on(const std::string& eventType, const DukValue& callback)
{
if (eventType == "close")
{
_onClose = callback;
}
else if (eventType == "data")
{
_onData = callback;
}
return this;
}
void CloseSocket()
{
if (_socket != nullptr)
{
_socket->Close();
_socket = nullptr;
RaiseOnClose(false);
}
}
void RaiseOnClose(bool hadError)
{
auto& scriptEngine = GetContext()->GetScriptEngine();
auto ctx = scriptEngine.GetContext();
scriptEngine.ExecutePluginCall(GetPlugin(), _onClose, { ToDuk(ctx, hadError) }, false);
}
void RaiseOnData(const std::string& data)
{
auto& scriptEngine = GetContext()->GetScriptEngine();
auto ctx = scriptEngine.GetContext();
scriptEngine.ExecutePluginCall(GetPlugin(), _onData, { ToDuk(ctx, data) }, false);
}
public:
void Update() override
{
if (_disposed)
return;
if (_socket != nullptr)
{
if (_socket->GetStatus() == SOCKET_STATUS_CONNECTED)
{
char buffer[128];
size_t bytesRead{};
auto result = _socket->ReceiveData(buffer, sizeof(buffer), &bytesRead);
switch (result)
{
case NETWORK_READPACKET_SUCCESS:
RaiseOnData(std::string(buffer, bytesRead));
break;
case NETWORK_READPACKET_NO_DATA:
break;
case NETWORK_READPACKET_MORE_DATA:
break;
case NETWORK_READPACKET_DISCONNECTED:
CloseSocket();
_disposed = true;
break;
}
}
else
{
CloseSocket();
_disposed = true;
}
}
}
void Dispose() override
{
CloseSocket();
_disposed = true;
}
bool IsDisposed() const override
{
return _disposed;
}
static void Register(duk_context* ctx)
{
dukglue_register_method(ctx, &ScSocket::destroy, "destroy");
dukglue_register_method(ctx, &ScSocket::end, "end");
dukglue_register_method(ctx, &ScSocket::write, "write");
dukglue_register_method(ctx, &ScSocket::on, "on");
}
};
class ScSocketServer : public ScSocketBase
{
private:
std::unique_ptr<ITcpSocket> _socket;
DukValue _onConnection;
std::vector<std::shared_ptr<ScSocket>> _scClientSockets;
bool _disposed{};
ScSocketServer* close()
{
Dispose();
return this;
}
ScSocketServer* listen(int32_t port, const DukValue& callback)
{
if (_disposed)
{
auto ctx = GetContext()->GetScriptEngine().GetContext();
duk_error(ctx, DUK_ERR_ERROR, "Socket is disposed.");
}
else
{
if (_socket == nullptr)
{
_socket = CreateTcpSocket();
}
if (_socket->GetStatus() == SOCKET_STATUS_LISTENING)
{
auto ctx = GetContext()->GetScriptEngine().GetContext();
duk_error(ctx, DUK_ERR_ERROR, "Server is already listening.");
}
else
{
_socket->Listen(port);
}
}
return this;
}
ScSocketServer* on(const std::string& eventType, const DukValue& callback)
{
if (eventType == "connection")
{
_onConnection = callback;
}
return this;
}
public:
ScSocketServer(const std::shared_ptr<Plugin>& plugin)
: ScSocketBase(plugin)
{
}
void Update() override
{
if (_disposed)
return;
if (_socket == nullptr)
return;
if (_socket->GetStatus() == SOCKET_STATUS_LISTENING)
{
auto client = _socket->Accept();
if (client != nullptr)
{
auto& scriptEngine = GetContext()->GetScriptEngine();
auto clientSocket = std::make_shared<ScSocket>(GetPlugin(), std::move(client));
scriptEngine.AddSocket(clientSocket);
auto ctx = scriptEngine.GetContext();
auto dukClientSocket = GetObjectAsDukValue(ctx, clientSocket);
scriptEngine.ExecutePluginCall(GetPlugin(), _onConnection, { dukClientSocket }, false);
}
}
}
void Dispose() override
{
if (_socket != nullptr)
{
_socket->Close();
_socket = nullptr;
}
_disposed = true;
}
bool IsDisposed() const override
{
return _disposed;
}
static void Register(duk_context* ctx)
{
dukglue_register_method(ctx, &ScSocketServer::close, "close");
dukglue_register_method(ctx, &ScSocketServer::listen, "listen");
dukglue_register_method(ctx, &ScSocketServer::on, "on");
}
};
} // namespace OpenRCT2::Scripting
#endif

View File

@@ -33,6 +33,7 @@
# include "ScObject.hpp"
# include "ScPark.hpp"
# include "ScRide.hpp"
# include "ScSocketServer.hpp"
# include "ScTile.hpp"
# include <iostream>
@@ -41,7 +42,7 @@
using namespace OpenRCT2;
using namespace OpenRCT2::Scripting;
static constexpr int32_t OPENRCT2_PLUGIN_API_VERSION = 3;
static constexpr int32_t OPENRCT2_PLUGIN_API_VERSION = 4;
struct ExpressionStringifier final
{
@@ -393,6 +394,8 @@ void ScriptEngine::Initialise()
ScVehicle::Register(ctx);
ScPeep::Register(ctx);
ScGuest::Register(ctx);
ScSocket::Register(ctx);
ScSocketServer::Register(ctx);
ScStaff::Register(ctx);
dukglue_register_global(ctx, std::make_shared<ScCheats>(), "cheats");
@@ -479,6 +482,7 @@ void ScriptEngine::StopPlugin(std::shared_ptr<Plugin> plugin)
if (plugin->HasStarted())
{
RemoveCustomGameActions(plugin);
RemoveSockets(plugin);
_hookEngine.UnsubscribeAll(plugin);
for (auto callback : _pluginStoppedSubscriptions)
{
@@ -640,6 +644,7 @@ void ScriptEngine::Update()
}
}
UpdateSockets();
ProcessREPL();
}
@@ -1127,6 +1132,41 @@ void ScriptEngine::SaveSharedStorage()
}
}
void ScriptEngine::AddSocket(const std::shared_ptr<ScSocketBase>& socket)
{
_sockets.push_back(socket);
}
void ScriptEngine::UpdateSockets()
{
// Use simple for i loop as Update calls can modify the list
for (size_t i = 0; i < _sockets.size(); i++)
{
_sockets[i]->Update();
if (_sockets[i]->IsDisposed())
{
_sockets.erase(_sockets.begin() + i);
i--;
}
}
}
void ScriptEngine::RemoveSockets(const std::shared_ptr<Plugin>& plugin)
{
for (auto it = _sockets.begin(); it != _sockets.end();)
{
if ((*it)->GetPlugin() == plugin)
{
(*it)->Dispose();
it = _sockets.erase(it);
}
else
{
it++;
}
}
}
std::string OpenRCT2::Scripting::Stringify(const DukValue& val)
{
return ExpressionStringifier::StringifyExpression(val);

View File

@@ -42,6 +42,8 @@ namespace OpenRCT2
namespace OpenRCT2::Scripting
{
class ScSocketBase;
class ScriptExecutionInfo
{
private:
@@ -133,6 +135,9 @@ namespace OpenRCT2::Scripting
};
std::unordered_map<std::string, CustomActionInfo> _customActions;
# ifndef DISABLE_NETWORK
std::vector<std::shared_ptr<ScSocketBase>> _sockets;
# endif
public:
ScriptEngine(InteractiveConsole& console, IPlatformEnvironment& env);
@@ -186,6 +191,8 @@ namespace OpenRCT2::Scripting
void SaveSharedStorage();
void AddSocket(const std::shared_ptr<ScSocketBase>& socket);
private:
void Initialise();
void StartPlugins();
@@ -206,6 +213,9 @@ namespace OpenRCT2::Scripting
void InitSharedStorage();
void LoadSharedStorage();
void UpdateSockets();
void RemoveSockets(const std::shared_ptr<Plugin>& plugin);
};
bool IsGameStateMutable();