mirror of
https://github.com/OpenRCT2/OpenRCT2
synced 2025-12-24 00:03:11 +01:00
Start implementing TCP API
This commit is contained in:
29
distribution/openrct2.d.ts
vendored
29
distribution/openrct2.d.ts
vendored
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>();
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
313
src/openrct2/scripting/ScSocketServer.hpp
Normal file
313
src/openrct2/scripting/ScSocketServer.hpp
Normal 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
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user