Skip to content

Commit

Permalink
- Fixed crash in context if the engine was shutdown while a context w…
Browse files Browse the repository at this point in the history
…as still suspended

- Implemented a script socket add-on
- asrun can now use sockets to setup a simple server, or connect to remote sockets

git-svn-id: http://svn.code.sf.net/p/angelscript/code/trunk@2951 404ce1b2-830e-0410-a2e2-b09542c77caf
  • Loading branch information
angelcode committed Sep 2, 2024
1 parent bc5eb66 commit 9fcfbc1
Show file tree
Hide file tree
Showing 13 changed files with 351 additions and 3 deletions.
257 changes: 257 additions & 0 deletions sdk/add_on/scriptsocket/scriptsocket.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
#include "scriptsocket.h"
#include <assert.h>

BEGIN_AS_NAMESPACE

#ifdef _WIN32

// Link with ws2_32.lib
#pragma comment(lib, "Ws2_32.lib")

// Manage the required calls to WSAStartup and WSACleanup
class WindowsSocketLib
{
public:
WindowsSocketLib() : m_status(0)
{
WORD wVersionRequested = 0x0202;
WSADATA wsadata;
// WSAStartup can be called multiple times, so it is not a problem if another piece of code also called it
m_status = WSAStartup(wVersionRequested, &wsadata);
if (m_status != 0)
{
// No usable WINSOCK.DLL found
}
else if (wsadata.wVersion != wVersionRequested)
{
// WINSOCK.DLL does not support version 2.2
WSACleanup();
m_status = -1;
}
}

~WindowsSocketLib()
{
// WSACleanup must be called for each successful call to WSAStartup
if( m_status == 0 )
WSACleanup();
}

int m_status;
} g_windowsSocketLib;
#endif

CScriptSocket::CScriptSocket() : m_refCount(1), m_socket(-1), m_isListening(false)
{
// TODO: On Windows check if the Windows Socket was properly loaded, else raise a script exception
}

void CScriptSocket::AddRef() const
{
asAtomicInc(m_refCount);;
}

void CScriptSocket::Release() const
{
if (asAtomicDec(m_refCount) == 0)
delete this;
}

CScriptSocket::~CScriptSocket()
{
// Disconnect the socket if it is open
Close();
}

int CScriptSocket::Listen(asWORD port)
{
// If another socket is already used it must first be closed, and the working thread must be shutdown
if (m_socket != -1)
return -1;

// Set up a listener socket
// TODO: Allow script to define the protocol
m_socket = (int)socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (m_socket == -1)
return -1;

sockaddr_in serverAddress = { 0 };
serverAddress.sin_family = AF_INET;
serverAddress.sin_port = htons(port);
serverAddress.sin_addr.s_addr = INADDR_ANY;

// TODO: Need to be able to tell if the port is already occupied
int r = bind(m_socket, (struct sockaddr*)&serverAddress, sizeof(serverAddress));
if (r == SOCKET_ERROR)
{
closesocket(m_socket);
m_socket = -1;
return -1;
}
m_isListening = true;

// TODO: Allow script to define the max queue for incoming connections
listen(m_socket, 5);

return 0;
}

CScriptSocket* CScriptSocket::Accept()
{
// Cannot accept a client on an ordinary socket or if the socket is not active
if (!m_isListening || m_socket == -1)
return 0;

// TODO: Should optionally allow setting a timeout for how long to wait
// First determine if there is anything to receive so that doesn't block
fd_set read = { 1, (SOCKET)m_socket };
TIMEVAL timeout = { 0,0 }; // Don't wait
int r = select(0, &read, 0, 0, &timeout);
if (r == 0)
return 0;

// TODO: need to be able to check to what ip address and port the socket is connected it
// Each incoming client connection a new CScriptSocket is created and put in the array so the script can retrieve them
int clientSocket = (int)accept(m_socket, 0, 0);
if (clientSocket != -1)
{
CScriptSocket* client = new CScriptSocket();
client->m_socket = clientSocket;
return client;
}

return 0;
}

int CScriptSocket::Close()
{
// If the socket is open
if (m_socket != -1)
return -1;

// Close the listener socket
closesocket(m_socket);
m_socket = -1;

return 0;
}

int CScriptSocket::Connect(asUINT ipv4Address, asWORD port)
{
// If another socket is already used it must first be closed, and the working thread must be shutdown
if (m_socket != -1)
return -1;

// Set up a client socket
// TODO: Allow script to define the protocol
m_socket = (int)socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

sockaddr_in serverAddress = { 0 };
serverAddress.sin_family = AF_INET;
serverAddress.sin_port = htons(port);
serverAddress.sin_addr.s_addr = htonl(ipv4Address);

// TODO: Allow script to define a timeout
int result = connect(m_socket, (struct sockaddr*)&serverAddress, sizeof(serverAddress));
if (result == SOCKET_ERROR)
{
closesocket(m_socket);
m_socket = -1;
return -1;
}

// TODO: Should the client socket already start receiving data? Or should it wait for the script to initiate that?

return 0;
}

int CScriptSocket::Send(const std::string& data)
{
// Cannot send on a listener socket or if the socket is not connected
if (m_isListening || m_socket == -1)
return -1;

// Send a buffer of data over the socket
// TODO: The sending of data should be done in a background thread so it can be done over a time rather than block the caller
// TODO: How to determine the maximum size of data that can be sent in a single call?
int r = send(m_socket, data.c_str(), (int)data.length(), 0);
if (r == SOCKET_ERROR)
return -1;

// Return the number of bytes sent
return r;
}

std::string CScriptSocket::Receive()
{
// Cannot receive on a listener socket or if the socket is not connected
if (m_isListening || m_socket == -1)
return "";

// TODO: Need to be able to set the size of the internal buffer

// TODO: Should optionally allow setting a timeout for how long to wait
// First determine if there is anything to receive so that doesn't block
char buf[1024] = {};
fd_set read = { 1, (SOCKET)m_socket };
TIMEVAL timeout = { 0,0 }; // Don't wait
int r = select(0, &read, 0, 0, &timeout);
if (r == 0)
return "";

std::string msg;
for (;;)
{
// Read the buffer
r = recv(m_socket, buf, sizeof(buf), 0);
if (r >= 0)
{
msg.append(buf, r);
break;
}
else if (r == SOCKET_ERROR)
{
r = WSAGetLastError();
if (r == WSAEMSGSIZE)
{
// Append what could be read, then read the rest
msg.append(buf, sizeof(buf));
}
else
break;
}
}

return msg;
}

static CScriptSocket* CScriptSocket_Factory()
{
return new CScriptSocket();
}

int RegisterScriptSocket(asIScriptEngine* engine)
{
int r;

// Check that the string type has been registered already
r = engine->GetTypeIdByDecl("string"); assert(r >= 0);

// Register the socket class with the script engine
engine->RegisterObjectType("socket", 0, asOBJ_REF);
r = engine->RegisterObjectBehaviour("socket", asBEHAVE_FACTORY, "socket @f()", asFUNCTION(CScriptSocket_Factory), asCALL_CDECL); assert(r >= 0);
r = engine->RegisterObjectBehaviour("socket", asBEHAVE_ADDREF, "void f()", asMETHOD(CScriptSocket, AddRef), asCALL_THISCALL); assert(r >= 0);
r = engine->RegisterObjectBehaviour("socket", asBEHAVE_RELEASE, "void f()", asMETHOD(CScriptSocket, Release), asCALL_THISCALL); assert(r >= 0);

r = engine->RegisterObjectMethod("socket", "int listen(uint16 port)", asMETHOD(CScriptSocket, Listen), asCALL_THISCALL); assert(r >= 0);
r = engine->RegisterObjectMethod("socket", "int close()", asMETHOD(CScriptSocket, Close), asCALL_THISCALL); assert(r >= 0);
r = engine->RegisterObjectMethod("socket", "socket @accept()", asMETHOD(CScriptSocket, Accept), asCALL_THISCALL); assert(r >= 0);
r = engine->RegisterObjectMethod("socket", "int connect(uint ipv4address, uint16 port)", asMETHOD(CScriptSocket, Connect), asCALL_THISCALL); assert(r >= 0);
r = engine->RegisterObjectMethod("socket", "int send(const string &in data)", asMETHOD(CScriptSocket, Send), asCALL_THISCALL); assert(r >= 0);
r = engine->RegisterObjectMethod("socket", "string receive()", asMETHOD(CScriptSocket, Receive), asCALL_THISCALL); assert(r >= 0);

return 0;
}

END_AS_NAMESPACE

54 changes: 54 additions & 0 deletions sdk/add_on/scriptsocket/scriptsocket.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//
// CScriptSocket
//
// This class represents a socket. It can be used to set up listeners for
// connecting incoming clients, and also to connect to a server. The socket
// class will create threads in the background to handle the communication
// and update a send and a receive buffer so the script will not have to deal
// with that.
//

#ifndef SCRIPTSOCKET_H
#define SCRIPTSOCKET_H

#include <string>
#include <list>
#ifdef _WIN32
#include <winsock2.h>
#endif

#ifndef ANGELSCRIPT_H
// Avoid having to inform include path if header is already include before
#include <angelscript.h>
#endif

BEGIN_AS_NAMESPACE

class CScriptSocket
{
public:
CScriptSocket();

void AddRef() const;
void Release() const;

int Listen(asWORD port);
int Close();
CScriptSocket* Accept();
int Connect(asUINT ipv4Address, asWORD port);
int Send(const std::string& data);
std::string Receive();

protected:
~CScriptSocket();
mutable int m_refCount;

int m_socket;
bool m_isListening;
};

int RegisterScriptSocket(asIScriptEngine* engine);

END_AS_NAMESPACE

#endif
12 changes: 11 additions & 1 deletion sdk/angelscript/source/as_context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5433,6 +5433,17 @@ bool asCContext::CleanStackFrame(bool catchException)
bool exceptionCaught = false;
asSTryCatchInfo *tryCatchInfo = 0;

if (m_currentFunction == 0)
return false;

if (m_currentFunction->funcType == asFUNC_SCRIPT && m_currentFunction->scriptData == 0)
{
asCString msg;
msg.Format(TXT_FUNC_s_RELEASED_BEFORE_CLEANUP, m_currentFunction->name.AddressOf());
m_engine->WriteMessage("", 0, 0, asMSGTYPE_ERROR, msg.AddressOf());
return false;
}

// Clean object variables on the stack
// If the stack memory is not allocated or the program pointer
// is not set, then there is nothing to clean up on the stack frame
Expand All @@ -5444,7 +5455,6 @@ bool asCContext::CleanStackFrame(bool catchException)

// Check if this function will catch the exception
// Try blocks can be nested, so use the innermost block
asASSERT(m_currentFunction->scriptData);
if (catchException && m_currentFunction->scriptData)
{
asUINT currPos = asUINT(m_regs.programPointer - m_currentFunction->scriptData->byteCode.AddressOf());
Expand Down
1 change: 1 addition & 0 deletions sdk/angelscript/source/as_texts.h
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,7 @@
#define TXT_ENGINE_REF_COUNT_ERROR_DURING_SHUTDOWN "Uh oh! The engine's reference count is increasing while it is being destroyed. Make sure references needed for clean-up are immediately released"
#define TXT_MODULE_IS_IN_USE "The module is still in use and cannot be rebuilt. Discard it and request another module"
#define TXT_EXTRNL_REF_TO_MODULE_s "There is an external reference to an object in module '%s', preventing it from being deleted"
#define TXT_FUNC_s_RELEASED_BEFORE_CLEANUP "The engine was shutdown before the context released. Function '%s' cannot be cleaned up"

// Internal names

Expand Down
7 changes: 5 additions & 2 deletions sdk/docs/articles/changes2.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@

<h1>AngelScript Change Log</h1>

<h2>Version 2.38.0 WIP - 2024/08/25</h2>
<h2>Version 2.38.0 WIP - 2024/09/02</h2>

<ul>
<li>Bug fixes
<ul>
<li>Use of computed gotos on gnuc and clang was not enabled by default (Thanks Asu)
<li>Fixed crash in context if the engine was shutdown while a context was still suspended
</ul>
<li>Library
<ul>
Expand All @@ -35,10 +36,12 @@ <h2>Version 2.38.0 WIP - 2024/08/25</h2>
<ul>
<li>It is now possible to instantiate registered template functions and call them (Thanks MindOfTony)
</ul>
<li>Add-ons
<li>Add-ons &amp; Samples
<ul>
<li>Made parseFloat in std::string add-on threadsafe (Thanks gjl)
<li>Fixed issue in script builder with metadata for variable declarations using direct object initialization (Thanks Miss)
<li>Implemented a script socket add-on
<li>asrun can now use sockets to setup a simple server, or connect to remote sockets
</ul>
<li>project
<ul>
Expand Down
2 changes: 2 additions & 0 deletions sdk/docs/doxygen/source/doc_addon.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ This page gives a brief description of the add-ons that you'll find in the /sdk/
- \subpage doc_addon_grid
- \subpage doc_addon_datetime
- \subpage doc_addon_helpers_try
\todo add socket
Expand Down
2 changes: 2 additions & 0 deletions sdk/docs/doxygen/source/doc_samples.h
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,8 @@ breakpoints, stepping through the code, examining variables, etc.
- \ref doc_script_stdlib_system
- Implementing a \#pragma callback
\todo add socket
\see \subpage doc_samples_asrun_manual
Expand Down
Loading

0 comments on commit 9fcfbc1

Please sign in to comment.