mirror of https://github.com/ClassiCube/ClassiCube
Web: Rewrite sockets API to avoid standard library
This commit is contained in:
parent
bb68bfd0cf
commit
e43c4ea690
60
license.txt
60
license.txt
|
|
@ -90,6 +90,66 @@ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
|||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
Emscripten license
|
||||
==============================================================================
|
||||
|
||||
Copyright (c) 2010-2014 Emscripten authors, see AUTHORS file.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
==============================================================================
|
||||
|
||||
Copyright (c) 2010-2014 Emscripten authors, see AUTHORS file.
|
||||
All rights reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of this software and associated documentation files (the
|
||||
"Software"), to deal with the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimers.
|
||||
|
||||
Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimers
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
|
||||
Neither the names of Mozilla,
|
||||
nor the names of its contributors may be used to endorse
|
||||
or promote products derived from this Software without specific prior
|
||||
written permission.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR
|
||||
ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE SOFTWARE.
|
||||
|
||||
==============================================================================
|
||||
|
||||
|
||||
FreeType license
|
||||
==================
|
||||
The FreeType Project LICENSE
|
||||
|
|
|
|||
|
|
@ -272,6 +272,7 @@
|
|||
<ClCompile Include="Graphics.c" />
|
||||
<ClCompile Include="Gui.c" />
|
||||
<ClCompile Include="HeldBlockRenderer.c" />
|
||||
<ClCompile Include="interop_web.c" />
|
||||
<ClCompile Include="Inventory.c" />
|
||||
<ClCompile Include="Launcher.c" />
|
||||
<ClCompile Include="LScreens.c" />
|
||||
|
|
|
|||
|
|
@ -545,5 +545,8 @@
|
|||
<ClCompile Include="Platform_Posix.c">
|
||||
<Filter>Source Files\Platform</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="interop_web.c">
|
||||
<Filter>Source Files\Platform</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
@ -10,7 +10,6 @@
|
|||
#include "Utils.h"
|
||||
#include "Errors.h"
|
||||
|
||||
/* POSIX can be shared between Linux/BSD/macOS */
|
||||
#include <errno.h>
|
||||
#include <time.h>
|
||||
#include <stdlib.h>
|
||||
|
|
@ -18,15 +17,9 @@
|
|||
#include <unistd.h>
|
||||
#include <dirent.h>
|
||||
#include <fcntl.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/time.h>
|
||||
#include <utime.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
|
||||
const cc_result ReturnCode_FileShareViolation = 1000000000; /* TODO: not used apparently */
|
||||
|
|
@ -73,9 +66,9 @@ cc_uint64 Stopwatch_ElapsedMicroseconds(cc_uint64 beg, cc_uint64 end) {
|
|||
return ((end - beg) * sw_freqMul) / sw_freqDiv;
|
||||
}
|
||||
|
||||
extern void interop_Log(const char* msg, int len);
|
||||
void Platform_Log(const char* msg, int len) {
|
||||
write(STDOUT_FILENO, msg, len);
|
||||
write(STDOUT_FILENO, "\n", 1);
|
||||
interop_Log(msg, len);
|
||||
}
|
||||
|
||||
#define UnixTime_TotalMS(time) ((cc_uint64)time.tv_sec * 1000 + UNIX_EPOCH + (time.tv_usec / 1000))
|
||||
|
|
@ -248,76 +241,98 @@ void Platform_LoadSysFonts(void) { }
|
|||
/*########################################################################################################################*
|
||||
*---------------------------------------------------------Socket----------------------------------------------------------*
|
||||
*#########################################################################################################################*/
|
||||
extern int interop_SocketCreate(void);
|
||||
extern int interop_SocketConnect(int sock, const char* addr, int port);
|
||||
extern int interop_SocketClose(int sock);
|
||||
extern int interop_SocketSend(int sock, const void* data, int len);
|
||||
extern int interop_SocketRecv(int sock, void* data, int len);
|
||||
extern int interop_SocketGetPending(int sock);
|
||||
extern int interop_SocketGetError(int sock);
|
||||
extern int interop_SocketPoll(int sock);
|
||||
|
||||
cc_result Socket_Create(cc_socket* s) {
|
||||
*s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
return *s == -1 ? errno : 0;
|
||||
*s = interop_SocketCreate();
|
||||
return 0;
|
||||
}
|
||||
|
||||
cc_result Socket_Available(cc_socket s, int* available) {
|
||||
return ioctl(s, FIONREAD, available);
|
||||
int res = interop_SocketGetPending(s);
|
||||
/* returned result is negative for error */
|
||||
|
||||
if (res >= 0) {
|
||||
*available = res; return 0;
|
||||
} else {
|
||||
*available = 0; return -res;
|
||||
}
|
||||
}
|
||||
cc_result Socket_SetBlocking(cc_socket s, cc_bool blocking) {
|
||||
return ERR_NOT_SUPPORTED; /* sockets always async */
|
||||
}
|
||||
|
||||
cc_result Socket_GetError(cc_socket s, cc_result* result) {
|
||||
socklen_t resultSize = sizeof(cc_result);
|
||||
return getsockopt(s, SOL_SOCKET, SO_ERROR, result, &resultSize);
|
||||
int res = interop_SocketGetError(s);
|
||||
/* returned result is negative for error */
|
||||
|
||||
if (res >= 0) {
|
||||
*result = res; return 0;
|
||||
} else {
|
||||
*result = 0; return -res;
|
||||
}
|
||||
}
|
||||
|
||||
cc_result Socket_Connect(cc_socket s, const cc_string* ip, int port) {
|
||||
struct sockaddr addr;
|
||||
cc_result res;
|
||||
addr.sa_family = AF_INET;
|
||||
|
||||
Stream_SetU16_BE( (cc_uint8*)&addr.sa_data[0], port);
|
||||
if (!Utils_ParseIP(ip, (cc_uint8*)&addr.sa_data[2]))
|
||||
return ERR_INVALID_ARGUMENT;
|
||||
|
||||
res = connect(s, &addr, sizeof(addr));
|
||||
return res == -1 ? errno : 0;
|
||||
char addr[NATIVE_STR_LEN];
|
||||
Platform_EncodeUtf8(addr, ip);
|
||||
return interop_SocketConnect(s, addr, port);
|
||||
}
|
||||
|
||||
cc_result Socket_Read(cc_socket s, cc_uint8* data, cc_uint32 count, cc_uint32* modified) {
|
||||
/* recv only reads one WebSocket frame at most, hence call it multiple times */
|
||||
int recvCount = 0, pending;
|
||||
*modified = 0;
|
||||
int res; *modified = 0;
|
||||
|
||||
while (count && !Socket_Available(s, &pending) && pending) {
|
||||
recvCount = recv(s, data, count, 0);
|
||||
if (recvCount == -1) return errno;
|
||||
while (count) {
|
||||
res = interop_SocketRecv(s, data, count);
|
||||
/* returned result is negative for error */
|
||||
|
||||
*modified += recvCount;
|
||||
data += recvCount; count -= recvCount;
|
||||
if (res >= 0) {
|
||||
*modified += res;
|
||||
data += res; count -= res;
|
||||
} else {
|
||||
/* EAGAIN when no data available */
|
||||
if (res == -EAGAIN) break;
|
||||
return -res;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
cc_result Socket_Write(cc_socket s, const cc_uint8* data, cc_uint32 count, cc_uint32* modified) {
|
||||
int sentCount = send(s, data, count, 0);
|
||||
if (sentCount != -1) { *modified = sentCount; return 0; }
|
||||
*modified = 0; return errno;
|
||||
int res = interop_SocketSend(s, data, count);
|
||||
/* returned result is negative for error */
|
||||
|
||||
if (res >= 0) {
|
||||
*modified = res; return 0;
|
||||
} else {
|
||||
*modified = 0; return -res;
|
||||
}
|
||||
}
|
||||
|
||||
cc_result Socket_Close(cc_socket s) {
|
||||
cc_result res = close(s);
|
||||
if (res == -1) res = errno;
|
||||
return res;
|
||||
/* returned result is negative for error */
|
||||
return -interop_SocketClose(s);
|
||||
}
|
||||
|
||||
#include <poll.h>
|
||||
cc_result Socket_Poll(cc_socket s, int mode, cc_bool* success) {
|
||||
struct pollfd pfd;
|
||||
int flags;
|
||||
int res = interop_SocketPoll(s), flags;
|
||||
/* returned result is negative for error */
|
||||
|
||||
pfd.fd = s;
|
||||
pfd.events = mode == SOCKET_POLL_READ ? POLLIN : POLLOUT;
|
||||
if (poll(&pfd, 1, 0) == -1) { *success = false; return errno; }
|
||||
|
||||
/* to match select, closed socket still counts as readable */
|
||||
flags = mode == SOCKET_POLL_READ ? (POLLIN | POLLHUP) : POLLOUT;
|
||||
*success = (pfd.revents & flags) != 0;
|
||||
if (res >= 0) {
|
||||
flags = mode == SOCKET_POLL_READ ? 0x01 : 0x02;
|
||||
*success = (res & flags) != 0;
|
||||
return 0;
|
||||
} else {
|
||||
*success = false; return -res;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -397,10 +412,13 @@ EMSCRIPTEN_KEEPALIVE void Platform_LogError(const char* msg) {
|
|||
}
|
||||
|
||||
extern void interop_InitModule(void);
|
||||
extern void interop_InitSockets(void);
|
||||
extern void interop_GetIndexedDBError(char* buffer);
|
||||
|
||||
void Platform_Init(void) {
|
||||
char tmp[64+1] = { 0 };
|
||||
interop_InitModule();
|
||||
interop_InitSockets();
|
||||
|
||||
/* Check if an error occurred when pre-loading IndexedDB */
|
||||
interop_GetIndexedDBError(tmp);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,8 @@
|
|||
#include "Core.h"
|
||||
/* Copyright 2010 The Emscripten Authors. All rights reserved. */
|
||||
/* Emscripten is available under two separate licenses, the MIT license and the */
|
||||
/* University of Illinois/NCSA Open Source License. Both these licenses can be */
|
||||
/* found in the LICENSE file. */
|
||||
|
||||
#ifdef CC_BUILD_WEB
|
||||
#include <emscripten/emscripten.h>
|
||||
|
|
@ -142,6 +146,207 @@ int interop_OpenTab(const char* url) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
void interop_Log(const char* msg, int len) {
|
||||
EM_ASM_({ Module.print(UTF8ArrayToString(HEAPU8, $0, $1)); }, msg, len);
|
||||
}
|
||||
|
||||
void interop_InitSockets(void) {
|
||||
EM_ASM({
|
||||
window.SOCKETS = {
|
||||
EBADF:-8,EISCONN:-30,ENOTCONN:-53,EAGAIN:-6,EWOULDBLOCK:-6,EHOSTUNREACH:-23,EINPROGRESS:-26,EALREADY:-7,ECONNRESET:-15,EINVAL:-28,ECONNREFUSED:-14,
|
||||
sockets = [],
|
||||
|
||||
createSocket:function() {
|
||||
var sock = {
|
||||
error: null, // Used in getsockopt for SOL_SOCKET/SO_ERROR test
|
||||
recv_queue: [],
|
||||
socket: null,
|
||||
};
|
||||
sockets.push(sock);
|
||||
|
||||
return (sockets.length - 1) | 0;
|
||||
},
|
||||
connect:function(fd, addr, port) {
|
||||
var sock = sockets[fd];
|
||||
if (!sock) return EBADF;
|
||||
|
||||
// early out if we're already connected / in the middle of connecting
|
||||
var ws = sock.socket;
|
||||
if (ws) {
|
||||
if (ws.readyState === ws.CONNECTING) return EALREADY;
|
||||
return EISCONN;
|
||||
}
|
||||
|
||||
// create the actual websocket object and connect
|
||||
try {
|
||||
var parts = addr.split('/');
|
||||
var url = 'ws://' + parts[0] + ":" + port + "/" + parts.slice(1).join('/');
|
||||
ws = new WebSocket(url, 'ClassiCube');
|
||||
ws.binaryType = 'arraybuffer';
|
||||
} catch (e) {
|
||||
return EHOSTUNREACH;
|
||||
}
|
||||
sock.socket = ws;
|
||||
|
||||
ws.onopen = function() {};
|
||||
ws.onclose = function() {};
|
||||
ws.onmessage = function(event) {
|
||||
var data = event.data;
|
||||
if (typeof data === 'string') {
|
||||
var encoder = new TextEncoder(); // should be utf-8
|
||||
data = encoder.encode(data); // make a typed array from the string
|
||||
} else {
|
||||
assert(data.byteLength !== undefined); // must receive an ArrayBuffer
|
||||
if (data.byteLength == 0) {
|
||||
// An empty ArrayBuffer will emit a pseudo disconnect event
|
||||
// as recv/recvmsg will return zero which indicates that a socket
|
||||
// has performed a shutdown although the connection has not been disconnected yet.
|
||||
return;
|
||||
} else {
|
||||
data = new Uint8Array(data); // make a typed array view on the array buffer
|
||||
}
|
||||
}
|
||||
sock.recv_queue.push(data);
|
||||
};
|
||||
ws.onerror = function(error) {
|
||||
// The WebSocket spec only allows a 'simple event' to be thrown on error,
|
||||
// so we only really know as much as ECONNREFUSED.
|
||||
sock.error = -ECONNREFUSED; // Used in getsockopt for SOL_SOCKET/SO_ERROR test.
|
||||
};
|
||||
// always "fail" in non-blocking mode
|
||||
return EINPROGRESS;
|
||||
},
|
||||
poll:function(fd) {
|
||||
var sock = sockets[fd];
|
||||
if (!sock) return EBADF;
|
||||
|
||||
var ws = sock.socket;
|
||||
if (!ws) return 0;
|
||||
var mask = 0;
|
||||
|
||||
if (sock.recv_queue.length || (ws.readyState === ws.CLOSING || ws.readyState === ws.CLOSED)) mask |= 1;
|
||||
if (ws.readyState === ws.OPEN) mask |= 2;
|
||||
return mask;
|
||||
},
|
||||
getPending:function(fd) {
|
||||
var sock = sockets[fd];
|
||||
if (!sock) return EBADF;
|
||||
|
||||
var bytes = 0;
|
||||
if (sock.recv_queue.length) {
|
||||
bytes = sock.recv_queue[0].data.length;
|
||||
}
|
||||
return bytes;
|
||||
},
|
||||
getError:function(fd) {
|
||||
var sock = sockets[fd];
|
||||
if (!sock) return EBADF;
|
||||
|
||||
return sock.error || 0;
|
||||
},
|
||||
close:function(fd) {
|
||||
var sock = sockets[fd];
|
||||
if (!sock) return EBADF;
|
||||
|
||||
try {
|
||||
sock.socket.close();
|
||||
} catch (e) {
|
||||
}
|
||||
delete sock.socket;
|
||||
return 0;
|
||||
},
|
||||
send:function(fd, src, length) {
|
||||
var sock = sockets[fd];
|
||||
if (!sock) return EBADF;
|
||||
|
||||
var ws = sock.socket;
|
||||
if (!ws || ws.readyState === ws.CLOSING || ws.readyState === ws.CLOSED) {
|
||||
return ENOTCONN;
|
||||
} else if (ws.readyState === ws.CONNECTING) {
|
||||
return EAGAIN;
|
||||
}
|
||||
var data = HEAP8.slice(src, src + length);
|
||||
|
||||
try {
|
||||
ws.send(data);
|
||||
return length;
|
||||
} catch (e) {
|
||||
return EINVAL;
|
||||
}
|
||||
},
|
||||
recv:function(fd, dst, length) {
|
||||
var sock = sockets[fd];
|
||||
if (!sock) return EBADF;
|
||||
|
||||
var packet = sock.recv_queue.shift();
|
||||
if (!packet) {
|
||||
var ws = sock.socket;
|
||||
|
||||
if (!ws || ws.readyState == = ws.CLOSING || ws.readyState == = ws.CLOSED) {
|
||||
return ENOTCONN;
|
||||
} else {
|
||||
// socket is in a valid state but truly has nothing available
|
||||
return EAGAIN;
|
||||
}
|
||||
}
|
||||
|
||||
// packet will be an ArrayBuffer if it's unadulterated, but if it's
|
||||
// requeued TCP data it'll be an ArrayBufferView
|
||||
var packetLength = packet.byteLength || packet.length;
|
||||
var packetOffset = packet.byteOffset || 0;
|
||||
var packetBuffer = packet.buffer || packet;
|
||||
var bytesRead = Math.min(length, packetLength);
|
||||
var msg = new Uint8Array(packetBuffer, packetOffset, bytesRead);
|
||||
|
||||
// push back any unread data for TCP connections
|
||||
if (bytesRead < packetLength) {
|
||||
var bytesRemaining = packetLength - bytesRead;
|
||||
packet = new Uint8Array(packetBuffer, packetOffset + bytesRead, bytesRemaining);
|
||||
sock.recv_queue.unshift(packet);
|
||||
}
|
||||
|
||||
HEAPU8.set(msg.buffer, dst);
|
||||
return msg.buffer.byteLength;
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
int interop_SocketCreate(void) {
|
||||
return EM_ASM_INT_V({ return SOCKETS.createSocket(); });
|
||||
}
|
||||
|
||||
int interop_SocketConnect(int sock, const char* addr, int port) {
|
||||
return EM_ASM_INT({
|
||||
var str = UTF8ToString($1);
|
||||
return SOCKETS.connect($0, str, $2);
|
||||
}, sock, addr, port);
|
||||
}
|
||||
|
||||
int interop_SocketClose(int sock) {
|
||||
return EM_ASM_INT({ return SOCKETS.close($0); }, sock);
|
||||
}
|
||||
|
||||
int interop_SocketSend(int sock, const void* data, int len) {
|
||||
return EM_ASM_INT({ return SOCKETS.send($0, $1, $2); }, sock, data, len);
|
||||
}
|
||||
|
||||
int interop_SocketRecv(int sock, void* data, int len) {
|
||||
return EM_ASM_INT({ return SOCKETS.recv($0, $1, $2); }, sock, data, len);
|
||||
}
|
||||
|
||||
int interop_SocketGetPending(int sock) {
|
||||
return EM_ASM_INT({ return SOCKETS.getPending($0); }, sock);
|
||||
}
|
||||
|
||||
int interop_SocketGetError(int sock) {
|
||||
return EM_ASM_INT({ return SOCKETS.getError($0); }, sock);
|
||||
}
|
||||
|
||||
int interop_SocketPoll(int sock) {
|
||||
return EM_ASM_INT({ return SOCKETS.poll($0); }, sock);
|
||||
}
|
||||
|
||||
|
||||
/*########################################################################################################################*
|
||||
*----------------------------------------------------------Window---------------------------------------------------------*
|
||||
|
|
|
|||
Loading…
Reference in New Issue