commit f8744a29e57d467583fdd0512ca8fd4cd3069a79
parent 2b58783da6e5023c2f2c6d30bb3c7bd3078ceb3f
Author: Michael Savage <mikejsavage@gmail.com>
Date: Wed, 5 Sep 2018 20:47:11 +0300
Move event loop to C to more closely match how Windows works
This means sockets have to be created/polled on the C side, which means
lua-ev and luasocket are both gone.
Also made script debugging a bit easier.
Diffstat:
24 files changed, 907 insertions(+), 209 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -1,4 +1,4 @@
-mudGangster
+mudgangster
build
release
gen.mk
diff --git a/Makefile b/Makefile
@@ -1,7 +1,7 @@
all: debug
.PHONY: debug asan release clean
-build/lua_bytecode.h: src/lua/action.lua src/lua/alias.lua src/lua/chat.lua src/lua/connect.lua src/lua/event.lua src/lua/gag.lua src/lua/handlers.lua src/lua/intercept.lua src/lua/interval.lua src/lua/macro.lua src/lua/main.lua src/lua/script.lua src/lua/serialize.lua src/lua/status.lua src/lua/sub.lua src/lua/utils.lua
+build/lua_bytecode.h: src/lua/action.lua src/lua/alias.lua src/lua/chat.lua src/lua/mud.lua src/lua/event.lua src/lua/gag.lua src/lua/handlers.lua src/lua/intercept.lua src/lua/interval.lua src/lua/macro.lua src/lua/main.lua src/lua/script.lua src/lua/serialize.lua src/lua/status.lua src/lua/sub.lua src/lua/utils.lua src/lua/socket.lua
@printf "\033[1;33mbuilding $@\033[0m\n"
@scripts/pack_lua.sh
diff --git a/make.lua b/make.lua
@@ -5,5 +5,5 @@ require( "scripts.gen_makefile" )
-- require( "libs.lua" )
-- require( "libs.lpeg" )
-bin( "mudgangster", { "src/main", "src/script", "src/textbox", "src/input", "src/x11" } )
+bin( "mudgangster", { "src/main", "src/script", "src/textbox", "src/input", "src/x11", "src/platform_network" } )
gcc_bin_ldflags( "mudgangster", "-lm -lX11 -llua" ) -- -Wl,-E" ) need to export symbols when vendoring
diff --git a/scripts/gen_makefile.lua b/scripts/gen_makefile.lua
@@ -14,7 +14,7 @@ local configs = {
toolchain = "msvc",
- cxxflags = "/I . /c /Oi /Gm- /GR- /EHa- /EHsc /nologo /DNOMINMAX /DWIN32_LEAN_AND_MEAN",
+ cxxflags = "/I src /c /Oi /Gm- /GR- /EHa- /EHsc /nologo /DNOMINMAX /DWIN32_LEAN_AND_MEAN",
ldflags = "user32.lib shell32.lib advapi32.lib dbghelp.lib /nologo",
warnings = "/W4 /wd4100 /wd4146 /wd4189 /wd4201 /wd4324 /wd4351 /wd4127 /wd4505 /wd4530 /wd4702 /D_CRT_SECURE_NO_WARNINGS",
},
@@ -38,7 +38,7 @@ local configs = {
toolchain = "gcc",
cxx = "g++",
- cxxflags = "-I . -c -x c++ -std=c++11 -msse2 -ffast-math -fno-exceptions -fno-rtti -fno-strict-aliasing -fno-strict-overflow -fdiagnostics-color",
+ cxxflags = "-I src -c -x c++ -std=c++11 -msse2 -ffast-math -fno-exceptions -fno-rtti -fno-strict-aliasing -fno-strict-overflow -fdiagnostics-color",
ldflags = "-lm -lpthread -ldl",
warnings = "-Wall -Wextra -Wno-unused-parameter -Wno-unused-function -Wshadow -Wcast-align -Wstrict-overflow -Wvla", -- -Wconversion
},
diff --git a/src/common.h b/src/common.h
@@ -4,13 +4,28 @@
#include <stdint.h>
#include <stddef.h>
#include <stdlib.h>
+#include <string.h>
#include <assert.h>
#include "config.h"
-#define STRL( x ) ( x ), sizeof( x ) - 1
+typedef uint8_t u8;
+typedef uint16_t u16;
+typedef uint32_t u32;
+
+#define FATAL( form, ... ) \
+ do { \
+ printf( "[FATAL] " form, ##__VA_ARGS__ ); \
+ abort(); \
+ } while( 0 )
#define STATIC_ASSERT( p ) static_assert( p, #p )
+#define ASSERT assert
+#define NONCOPYABLE( T ) T( const T & ) = delete; void operator=( const T & ) = delete;
+
+template< typename T, size_t N >
+char ( &ArrayCountObj( const T ( & )[ N ] ) )[ N ];
+#define ARRAY_COUNT( arr ) ( sizeof( ArrayCountObj( arr ) ) )
template< typename T >
constexpr T min( T a, T b ) {
@@ -35,3 +50,15 @@ inline To checked_cast( const From & from ) {
assert( From( result ) == from );
return result;
}
+
+template< typename T >
+inline T * malloc_array( size_t count ) {
+ ASSERT( SIZE_MAX / count >= sizeof( T ) );
+ return ( T * ) malloc( count * sizeof( T ) );
+}
+
+template< typename T >
+inline T * realloc_array( T * old, size_t count ) {
+ ASSERT( SIZE_MAX / count >= sizeof( T ) );
+ return ( T * ) realloc( old, count * sizeof( T ) );
+}
diff --git a/src/lua/chat.lua b/src/lua/chat.lua
@@ -1,5 +1,3 @@
-local loop = ev.Loop.default
-
local handleChat
local CommandBytes = {
@@ -61,11 +59,9 @@ end
local Client = { }
-function Client:new( socket, address, port )
- socket:setoption( "keepalive", true )
-
+function Client:new( sock, address, port )
local client = {
- socket = socket,
+ socket = sock,
address = address,
port = port,
@@ -80,7 +76,7 @@ function Client:new( socket, address, port )
table.insert( Clients, client )
- socket:send( "CHAT:Hirve\n127.0.0.14050 " )
+ socket.send( sock, "CHAT:Hirve\n127.0.0.14050 " )
return client
end
@@ -93,7 +89,7 @@ function Client:kill()
mud.print( "\n#s> Disconnected from %s!", self.name )
self.state = "killed"
- self.socket:shutdown()
+ socket.close( self.socket )
for i, client in ipairs( Clients ) do
if client == self then
@@ -123,7 +119,7 @@ function mud.chatns( form, ... )
for _, client in ipairs( Clients ) do
if client.state == "connected" then
- client.socket:send( data )
+ socket.send( client.socket, data )
end
end
@@ -138,31 +134,22 @@ end
local function call( address, port )
mud.print( "\n#s> Calling %s:%d...", address, port )
- local sock = socket.tcp()
- sock:settimeout( 0 )
-
- sock:connect( address, port )
-
- ev.IO.new( function( loop, watcher )
- local _, err = sock:receive( "*a" )
-
- if err ~= "connection refused" then
- local client = Client:new( sock, address, port )
-
- ev.IO.new( function( loop, watcher )
- dataHandler( client, loop, watcher )
- end, sock:getfd(), ev.READ ):start( loop )
-
- ev.IO.new( function( loop, watcher )
- client.socket:send( CommandBytes.version .. "MudGangster" .. "\255" )
- watcher:stop( loop )
- end, sock:getfd(), ev.WRITE ):start( loop )
+ local client
+ local sock, err = socket.connect( address, port, function( sock, data )
+ if data then
+ assert( coroutine.resume( client.handler, data ) )
else
- mud.print( "\n#s> Failed to call %s:%d", address, port )
+ client:kill()
end
+ end )
- watcher:stop( loop )
- end, sock:getfd(), ev.WRITE ):start( loop )
+ if not sock then
+ mud.print( "\n#s> Connection failed: %s", err )
+ return
+ end
+
+ client = Client:new( sock, address, port )
+ socket.send( client.socket, CommandBytes.version .. "MudGangster" .. "\255" )
end
mud.alias( "/call", {
@@ -187,7 +174,7 @@ local function sendPM( client, message )
local named = "\nHirve chats to you, '" .. message .. "'"
local data = CommandBytes.pm .. named .. "\n\255"
- client.socket:send( data )
+ socket.send( client.socket, data )
end
mud.alias( "/silentpm", {
diff --git a/src/lua/connect.lua b/src/lua/connect.lua
@@ -1,113 +0,0 @@
-local DataHandler
-
-local LastAddress
-local LastPort
-
-local loop = ev.Loop.default
-
-function mud.disconnect()
- if not mud.connected then
- mud.print( "\n#s> You're not connected..." )
-
- return
- end
-
- mud.connected = false
-
- mud.kill()
-end
-
-function mud.connect( address, port )
- if mud.connected then
- mud.print( "\n#s> Already connected! (%s:%d)", LastAddress, LastPort )
-
- return
- end
-
- if not address then
- if not LastAddress then
- mud.print( "\n#s> I need an address..." )
-
- return
- end
-
- address = LastAddress
- port = LastPort
- end
-
- local sock = socket.tcp()
- sock:settimeout( 0 )
- sock:connect( address, port )
-
- mud.print( "\n#s> Connecting to %s:%d...", address, port )
-
- mud.connected = true
- mud.lastInput = mud.now()
-
- mud.send = function( data )
- mud.lastInput = mud.now()
-
- sock:send( data )
- end
-
- mud.kill = function()
- sock:shutdown()
- end
-
- ev.IO.new( function( loop, watcher )
- local _, err = sock:receive( "*a" )
-
- if err ~= "connection refused" then
- LastAddress = address
- LastPort = port
-
- ev.IO.new( function( loop, watcher )
- local _, err, data = sock:receive( "*a" )
-
- if not data then
- data = _
- end
-
- if data then
- DataHandler( data )
- mud.handleXEvents()
- end
-
- if err == "closed" then
- mud.print( "\n#s> Disconnected!" )
-
- watcher:stop( loop )
- sock:shutdown()
-
- mud.connected = false
-
- return
- end
- end, sock:getfd(), ev.READ ):start( loop )
- else
- mud.print( "\n#s> Refused!" )
-
- mud.connected = false
- end
-
- watcher:stop( loop )
- end, sock:getfd(), ev.WRITE ):start( loop )
-end
-
-mud.alias( "/con", {
- [ "^$" ] = function()
- mud.connect()
- end,
-
- [ "^(%S+)%s+(%d+)$" ] = function( address, port )
- mud.connect( address, port )
- end,
-}, "<ip> <port>" )
-
-mud.alias( "/dc", mud.disconnect )
-
-return {
- init = function( dataHandler )
- DataHandler = dataHandler
- end,
-}
diff --git a/src/lua/handlers.lua b/src/lua/handlers.lua
@@ -254,6 +254,7 @@ local function handleCommand( input, hide )
end
end
+ print( mud.send )
mud.send( input .. "\n" )
mud.drawMain()
end
@@ -284,8 +285,6 @@ local function handleMacro( key, shift, ctrl, alt )
end
local function handleClose()
- ev.Loop.default:unloop()
-
mud.event( "shutdown" )
end
@@ -295,5 +294,6 @@ return {
input = handleInput,
macro = handleMacro,
interval = interval.doIntervals,
+ socket = handleSocketData,
close = handleClose,
}
diff --git a/src/lua/interval.lua b/src/lua/interval.lua
@@ -1,7 +1,7 @@
local Intervals = { }
-local function doIntervals( loop, watcher )
- local now = loop:update_now()
+local function doIntervals()
+ local now = mud.now()
for i = 1, #Intervals do
local event = Intervals[ i ]
@@ -12,10 +12,6 @@ local function doIntervals( loop, watcher )
end
end
-function mud.now()
- return ev.Loop.default:update_now()
-end
-
function mud.interval( callback, interval, disabled )
enforce( callback, "callback", "function" )
enforce( interval, "interval", "number" )
diff --git a/src/lua/main.lua b/src/lua/main.lua
@@ -2,44 +2,32 @@ mud = {
connected = false,
}
--- local function luarocks_path( opt, orig )
--- local pipe = io.popen( "luarocks path " .. opt, "r" )
--- if not pipe then
--- return nil
--- end
---
--- local contents = pipe:read( "*all" )
--- pipe:close()
---
--- if not contents then
--- return orig
--- end
---
--- return contents:gsub( "%s*$", "" ) .. ";" .. orig
--- end
---
--- package.path = luarocks_path( "--lr-path", package.path )
--- package.cpath = luarocks_path( "--lr-cpath", package.cpath )
-
table.unpack = table.unpack or unpack
require( "utils" )
-socket = require( "socket" )
-ev = require( "ev" )
-
require( "event" )
local script = require( "script" )
local handlers = require( "handlers" )
-require( "chat" ).init( handlers.chat )
-require( "connect" ).init( handlers.data )
-
-local xFD, handleXEvents,
+local handleXEvents,
printMain, newlineMain, drawMain,
printChat, newlineChat, drawChat,
- setHandlers, urgent, setStatus = ...
+ setHandlers, urgent, setStatus,
+ sock_connect, sock_send, sock_close,
+ get_time = ...
+
+local socket_api = {
+ connect = sock_connect,
+ send = sock_send,
+ close = sock_close,
+}
+
+local socket_data_handler = require( "socket" ).init( socket_api )
+
+require( "mud" ).init( handlers.data )
+require( "chat" ).init( handlers.chat )
mud.printMain = printMain
mud.newlineMain = newlineMain
@@ -50,20 +38,12 @@ mud.newlineChat = newlineChat
mud.drawChat = drawChat
mud.urgent = urgent
+mud.now = get_time
require( "status" ).init( setStatus )
-setHandlers( handlers.input, handlers.macro, handlers.close )
-
-local loop = ev.Loop.default
+setHandlers( handlers.input, handlers.macro, handlers.close, socket_data_handler, handlers.interval )
mud.handleXEvents = handleXEvents
-ev.IO.new( handleXEvents, xFD, ev.READ ):start( loop )
-ev.Timer.new( handlers.interval, 0.5, 0.5 ):start( loop )
-
script.load()
-
-loop:loop()
-
-script.close()
diff --git a/src/lua/mud.lua b/src/lua/mud.lua
@@ -0,0 +1,81 @@
+local DataHandler
+local mud_socket
+
+local LastAddress
+local LastPort
+
+function mud.send( data )
+ mud.lastInput = mud.now()
+ socket.send( mud_socket, data )
+end
+
+function mud.disconnect()
+ mud.print( "\n#s> Disconnected!" )
+ mud.connected = false
+ socket.close( mud_socket )
+ mud_socket = nil
+end
+
+function mud.connect( address, port )
+ if mud.connected then
+ mud.print( "\n#s> Already connected! (%s:%d)", LastAddress, LastPort )
+
+ return
+ end
+
+ if not address then
+ if not LastAddress then
+ mud.print( "\n#s> I need an address..." )
+
+ return
+ end
+
+ address = LastAddress
+ port = LastPort
+ end
+
+ mud.print( "\n#s> Connecting to %s:%d...", address, port )
+
+ local sock, err = socket.connect( address, port, function( sock, data )
+ if data then
+ DataHandler( data )
+ else
+ mud.kill()
+ end
+ end )
+
+ if not sock then
+ mud.print( "\n#s> Connection failed: %s", err )
+ return
+ end
+
+ LastAddress = address
+ LastPort = port
+
+ mud.connected = true
+ mud.lastInput = mud.now()
+end
+
+mud.alias( "/con", {
+ [ "^$" ] = function()
+ mud.connect()
+ end,
+
+ [ "^(%S+)%s+(%d+)$" ] = function( address, port )
+ mud.connect( address, port )
+ end,
+}, "<ip> <port>" )
+
+mud.alias( "/dc", function()
+ if mud.connected then
+ mud.disconnect()
+ else
+ mud.print( "\n#s> You're not connected..." )
+ end
+end )
+
+return {
+ init = function( dataHandler )
+ DataHandler = dataHandler
+ end,
+}
diff --git a/src/lua/socket.lua b/src/lua/socket.lua
@@ -0,0 +1,36 @@
+local socket_api
+local data_callbacks = { }
+
+local function connect( addr, port, cb )
+ local sock, err = socket_api.connect( addr, port )
+ if not sock then
+ return nil, err
+ end
+ data_callbacks[ sock ] = cb
+ return sock
+end
+
+local function close( sock )
+ socket_api.close( sock )
+ data_callbacks[ sock ] = nil
+end
+
+local function on_socket_data( sock, data )
+ -- data could be like { type = "data/failed/close/etc", data = ... }
+ -- need a failed to handle async connect
+ data_callbacks[ sock ]( sock, data )
+end
+
+return {
+ init = function( api )
+ socket_api = api
+
+ socket = {
+ connect = connect,
+ send = socket_api.send,
+ close = close,
+ }
+
+ return on_socket_data
+ end,
+}
diff --git a/src/main.cc b/src/main.cc
@@ -7,7 +7,7 @@ int main() {
input_init();
script_init();
- // main loop is done in lua
+ event_loop();
script_term();
input_term();
diff --git a/src/platform_network.cc b/src/platform_network.cc
@@ -0,0 +1,262 @@
+#include "common.h"
+
+#include "platform.h"
+#include "platform_network.h"
+
+struct sockaddr_storage;
+
+static NetAddress sockaddr_to_netaddress( const struct sockaddr_storage & ss );
+static struct sockaddr_storage netaddress_to_sockaddr( const NetAddress & addr );
+static void setsockoptone( OSSocket fd, int level, int opt );
+
+#if PLATFORM_WINDOWS
+#include "win32_network.cc"
+#elif PLATFORM_UNIX
+#include "unix_network.cc"
+#else
+#error new platform
+#endif
+
+bool operator==( const NetAddress & lhs, const NetAddress & rhs ) {
+ if( lhs.type != rhs.type ) return false;
+ if( lhs.port != rhs.port ) return false;
+ if( lhs.type == NET_IPV4 ) return memcmp( &lhs.ipv4, &rhs.ipv4, sizeof( lhs.ipv4 ) ) == 0;
+ return memcmp( &lhs.ipv6, &rhs.ipv6, sizeof( lhs.ipv6 ) ) == 0;
+}
+
+bool operator!=( const NetAddress & lhs, const NetAddress & rhs ) {
+ return !( lhs == rhs );
+}
+
+static socklen_t sockaddr_size( const struct sockaddr_storage & ss ) {
+ return ss.ss_family == AF_INET ? sizeof( struct sockaddr_in ) : sizeof( struct sockaddr_in6 );
+}
+
+static NetAddress sockaddr_to_netaddress( const struct sockaddr_storage & ss ) {
+ NetAddress addr;
+ if( ss.ss_family == AF_INET ) {
+ const struct sockaddr_in & sa4 = ( const struct sockaddr_in & ) ss;
+
+ addr.type = NET_IPV4;
+ addr.port = ntohs( sa4.sin_port );
+ memcpy( &addr.ipv4, &sa4.sin_addr.s_addr, sizeof( addr.ipv4 ) );
+ }
+ else {
+ const struct sockaddr_in6 & sa6 = ( const struct sockaddr_in6 & ) ss;
+
+ addr.type = NET_IPV6;
+ addr.port = ntohs( sa6.sin6_port );
+ memcpy( &addr.ipv6, &sa6.sin6_addr.s6_addr, sizeof( addr.ipv6 ) );
+ }
+ return addr;
+}
+
+static struct sockaddr_storage netaddress_to_sockaddr( const NetAddress & addr ) {
+ struct sockaddr_storage ss;
+ if( addr.type == NET_IPV4 ) {
+ struct sockaddr_in & sa4 = ( struct sockaddr_in & ) ss;
+
+ ss.ss_family = AF_INET;
+ sa4.sin_port = htons( addr.port );
+ memcpy( &sa4.sin_addr.s_addr, &addr.ipv4, sizeof( addr.ipv4 ) );
+ }
+ else {
+ struct sockaddr_in6 & sa6 = ( struct sockaddr_in6 & ) ss;
+
+ ss.ss_family = AF_INET6;
+ sa6.sin6_port = htons( addr.port );
+ memcpy( &sa6.sin6_addr.s6_addr, &addr.ipv6, sizeof( addr.ipv6 ) );
+ }
+ return ss;
+}
+
+static void setsockoptone( OSSocket fd, int level, int opt ) {
+ int one = 1;
+ int ok = setsockopt( fd, level, opt, ( char * ) &one, sizeof( one ) );
+ if( ok == -1 ) {
+ FATAL( "setsockopt" );
+ }
+}
+
+UDPSocket net_new_udp( NonblockingBool nonblocking, u16 port ) {
+ UDPSocket sock;
+
+ sock.ipv4 = socket( AF_INET, SOCK_DGRAM, IPPROTO_UDP );
+ if( sock.ipv4 == INVALID_SOCKET ) {
+ FATAL( "socket" );
+ }
+ sock.ipv6 = socket( AF_INET6, SOCK_DGRAM, IPPROTO_UDP );
+ if( sock.ipv6 == INVALID_SOCKET ) {
+ FATAL( "socket" );
+ }
+
+ if( nonblocking == NET_NONBLOCKING ) {
+ make_socket_nonblocking( sock.ipv4 );
+ make_socket_nonblocking( sock.ipv6 );
+ }
+
+ if( port != 0 ) {
+ setsockoptone( sock.ipv4, SOL_SOCKET, SO_REUSEADDR );
+ setsockoptone( sock.ipv6, SOL_SOCKET, SO_REUSEADDR );
+ setsockoptone( sock.ipv6, IPPROTO_IPV6, IPV6_V6ONLY );
+ }
+
+ platform_init_sock( sock.ipv4 );
+ platform_init_sock( sock.ipv6 );
+
+ {
+ sockaddr_in my_addr4;
+ my_addr4.sin_family = AF_INET;
+ my_addr4.sin_port = htons( port );
+ my_addr4.sin_addr.s_addr = htonl( INADDR_ANY );
+ int ok = bind( sock.ipv4, ( struct sockaddr * ) &my_addr4, sizeof( my_addr4 ) );
+ if( ok == SOCKET_ERROR ) {
+ FATAL( "bind" );
+ }
+ }
+
+ {
+ sockaddr_in6 my_addr6;
+ my_addr6.sin6_family = AF_INET6;
+ my_addr6.sin6_port = htons( port );
+ my_addr6.sin6_addr = in6addr_any;
+ int ok = bind( sock.ipv6, ( struct sockaddr * ) &my_addr6, sizeof( my_addr6 ) );
+ if( ok == SOCKET_ERROR ) {
+ FATAL( "bind" );
+ }
+ }
+
+ return sock;
+}
+
+void net_send( UDPSocket sock, const void * data, size_t len, const NetAddress & addr ) {
+ struct sockaddr_storage ss = netaddress_to_sockaddr( addr );
+ socklen_t ss_size = sockaddr_size( ss );
+ OSSocket fd = addr.type == NET_IPV4 ? sock.ipv4 : sock.ipv6;
+ ssize_t ok = sendto( fd, ( const char * ) data, checked_cast< int >( len ), NET_SEND_FLAGS, ( struct sockaddr * ) &ss, ss_size );
+ if( ok == SOCKET_ERROR ) {
+ FATAL( "sendto" );
+ }
+}
+
+void net_destroy( UDPSocket * sock ) {
+ int ok4 = closesocket( sock->ipv4 );
+ if( ok4 == -1 ) {
+ FATAL( "closesocket" );
+ }
+ int ok6 = closesocket( sock->ipv6 );
+ if( ok6 == -1 ) {
+ FATAL( "closesocket" );
+ }
+ sock->ipv4 = INVALID_SOCKET;
+ sock->ipv6 = INVALID_SOCKET;
+}
+
+bool net_new_tcp( TCPSocket * sock, const NetAddress & addr, NonblockingBool nonblocking ) {
+ struct sockaddr_storage ss = netaddress_to_sockaddr( addr );
+ socklen_t ss_size = sockaddr_size( ss );
+
+ sock->fd = socket( ss.ss_family, SOCK_STREAM, IPPROTO_TCP );
+ if( sock->fd == INVALID_SOCKET ) {
+ FATAL( "socket" );
+ }
+
+ int ok = connect( sock->fd, ( const sockaddr * ) &ss, ss_size );
+ if( ok == -1 ) {
+ int ok_close = closesocket( sock->fd );
+ if( ok_close == -1 ) {
+ FATAL( "closesocket" );
+ }
+ // TODO: check for actual coding errors too
+ return false;
+ }
+
+ if( nonblocking == NET_NONBLOCKING ) {
+ make_socket_nonblocking( sock->fd );
+ }
+
+ setsockoptone( sock->fd, SOL_SOCKET, SO_KEEPALIVE );
+
+ platform_init_sock( sock->fd );
+
+ return true;
+}
+
+bool net_send( TCPSocket sock, const void * data, size_t len ) {
+ ssize_t sent = send( sock.fd, ( const char * ) data, len, NET_SEND_FLAGS );
+ if( sent < 0 ) return false;
+ return checked_cast< size_t >( sent ) == len;
+}
+
+TCPRecvResult net_recv( TCPSocket sock, void * buf, size_t buf_size, size_t * bytes_read, u32 timeout_ms ) {
+ while( true ) {
+ if( timeout_ms > 0 ) {
+ fd_set fds;
+ FD_ZERO( &fds );
+ FD_SET( sock.fd, &fds );
+
+ struct timeval tv;
+ tv.tv_sec = timeout_ms / 1000;
+ tv.tv_usec = ( timeout_ms % 1000 ) * 1000;
+
+ int ok = select( sock.fd + 1, &fds, NULL, NULL, &tv );
+ // TODO: update timeout
+ if( ok == 0 ) {
+ return TCP_TIMEOUT;
+ }
+ if( ok == -1 ) {
+ if( errno != EINTR ) continue;
+ FATAL( "select" );
+ }
+ }
+
+ ssize_t r = recv( sock.fd, ( char * ) buf, buf_size, 0 );
+ // TODO: this is not right on windows
+ if( r == -1 ) {
+ if( errno == EINTR ) continue;
+ if( errno == ECONNRESET ) return TCP_ERROR;
+ FATAL( "recv" );
+ }
+
+ *bytes_read = checked_cast< size_t >( r );
+ return r == 0 ? TCP_CLOSED : TCP_OK;
+ }
+}
+
+void net_destroy( TCPSocket * sock ) {
+ int ok = closesocket( sock->fd );
+ if( ok == -1 ) {
+ FATAL( "closesocket" );
+ }
+ sock->fd = INVALID_SOCKET;
+}
+
+size_t dns( const char * host, NetAddress * out, size_t n ) {
+ struct addrinfo hints;
+ memset( &hints, 0, sizeof( struct addrinfo ) );
+ hints.ai_family = AF_UNSPEC;
+ // TODO: figure out why ivp6 doesn't work on windows
+ hints.ai_family = AF_INET;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = IPPROTO_TCP;
+
+ struct addrinfo * addresses;
+ int ok = getaddrinfo( host, "http", &hints, &addresses );
+ if( ok != 0 )
+ return 0;
+
+ struct addrinfo * cursor = addresses;
+ size_t i = 0;
+ while( cursor != NULL && i < n ) {
+ out[ i ] = sockaddr_to_netaddress( *( struct sockaddr_storage * ) cursor->ai_addr );
+ cursor = cursor->ai_next;
+ i++;
+ }
+ freeaddrinfo( addresses );
+
+ return i;
+}
+
+bool dns_first( const char * host, NetAddress * address ) {
+ return dns( host, address, 1 ) == 1;
+}
diff --git a/src/platform_network.h b/src/platform_network.h
@@ -0,0 +1,60 @@
+#pragma once
+
+#include "platform.h"
+
+#if PLATFORM_WINDOWS
+typedef s64 OSSocket;
+#elif PLATFORM_UNIX
+typedef int OSSocket;
+#else
+#error new platform
+#endif
+
+enum TransportProtocol { NET_UDP, NET_TCP };
+enum IPvX { NET_IPV4, NET_IPV6 };
+enum NonblockingBool { NET_BLOCKING, NET_NONBLOCKING };
+
+enum TCPRecvResult {
+ TCP_OK,
+ TCP_TIMEOUT,
+ TCP_CLOSED,
+ TCP_ERROR,
+};
+
+struct UDPSocket {
+ OSSocket ipv4, ipv6;
+};
+
+struct TCPSocket {
+ OSSocket fd;
+};
+
+struct IPv4 { u8 bytes[ 4 ]; };
+struct IPv6 { u8 bytes[ 16 ]; };
+
+struct NetAddress {
+ IPvX type;
+ union {
+ IPv4 ipv4;
+ IPv6 ipv6;
+ };
+ u16 port;
+};
+
+bool operator==( const NetAddress & lhs, const NetAddress & rhs );
+bool operator!=( const NetAddress & lhs, const NetAddress & rhs );
+
+void net_init();
+void net_term();
+
+UDPSocket net_new_udp( NonblockingBool nonblocking, u16 port = 0 );
+void net_send( UDPSocket sock, const void * data, size_t len, const NetAddress & addr );
+bool net_tryrecv( UDPSocket sock, void * buf, size_t len, NetAddress * addr, size_t * bytes_received );
+void net_destroy( UDPSocket * sock );
+
+bool net_new_tcp( TCPSocket * sock, const NetAddress & addr, NonblockingBool nonblocking );
+bool net_send( TCPSocket sock, const void * data, size_t len );
+TCPRecvResult net_recv( TCPSocket sock, void * buf, size_t buf_size, size_t * bytes_read, u32 timeout_ms = 0 );
+void net_destroy( TCPSocket * sock );
+
+bool dns_first( const char * host, NetAddress * address );
diff --git a/src/platform_time.h b/src/platform_time.h
@@ -0,0 +1,13 @@
+#pragma once
+
+#include "platform.h"
+
+#if PLATFORM_WINDOWS
+#include "win32_time.h"
+#elif PLATFORM_OSX
+#include "darwin_time.h"
+#elif PLATFORM_UNIX
+#include "unix_time.h"
+#else
+#error new platform
+#endif
diff --git a/src/script.cc b/src/script.cc
@@ -2,6 +2,8 @@
#include "platform.h"
#include "ui.h"
+#include "platform_time.h"
+
#include <lua.hpp>
#if LUA_VERSION_NUM < 502
@@ -9,7 +11,7 @@
#endif
static const uint8_t lua_bytecode[] = {
-#include "build/lua_bytecode.h"
+#include "../build/lua_bytecode.h"
};
static lua_State * lua;
@@ -17,6 +19,17 @@ static lua_State * lua;
static int inputHandlerIdx = LUA_NOREF;
static int macroHandlerIdx = LUA_NOREF;
static int closeHandlerIdx = LUA_NOREF;
+static int socketHandlerIdx = LUA_NOREF;
+static int intervalHandlerIdx = LUA_NOREF;
+
+static void pcall( int args, const char * err ) {
+ if( lua_pcall( lua, args, 0, 1 ) ) {
+ printf( "%s: %s\n", err, lua_tostring( lua, -1 ) );
+ exit( 1 );
+ }
+
+ assert( lua_gettop( lua ) == 1 );
+}
void script_handleInput( const char * buffer, int len ) {
assert( inputHandlerIdx != LUA_NOREF );
@@ -24,10 +37,12 @@ void script_handleInput( const char * buffer, int len ) {
lua_rawgeti( lua, LUA_REGISTRYINDEX, inputHandlerIdx );
lua_pushlstring( lua, buffer, len );
- lua_call( lua, 1, 0 );
+ pcall( 1, "script_handleInput" );
}
void script_doMacro( const char * key, int len, bool shift, bool ctrl, bool alt ) {
+ assert( macroHandlerIdx != LUA_NOREF );
+
lua_rawgeti( lua, LUA_REGISTRYINDEX, macroHandlerIdx );
lua_pushlstring( lua, key, len );
@@ -36,15 +51,35 @@ void script_doMacro( const char * key, int len, bool shift, bool ctrl, bool alt
lua_pushboolean( lua, ctrl );
lua_pushboolean( lua, alt );
- lua_call( lua, 4, 0 );
+ pcall( 4, "script_doMacro" );
}
void script_handleClose() {
assert( closeHandlerIdx != LUA_NOREF );
lua_rawgeti( lua, LUA_REGISTRYINDEX, closeHandlerIdx );
+ pcall( 0, "script_handleClose" );
+}
+
+void script_socketData( void * sock, const char * data, size_t len ) {
+ assert( socketHandlerIdx != LUA_NOREF );
+
+ lua_rawgeti( lua, LUA_REGISTRYINDEX, socketHandlerIdx );
- lua_call( lua, 0, 0 );
+ lua_pushlightuserdata( lua, sock );
+ if( data == NULL )
+ lua_pushnil( lua );
+ else
+ lua_pushlstring( lua, data, len );
+
+ pcall( 2, "script_socketData" );
+}
+
+void script_fire_intervals() {
+ assert( intervalHandlerIdx != LUA_NOREF );
+
+ lua_rawgeti( lua, LUA_REGISTRYINDEX, intervalHandlerIdx );
+ pcall( 0, "script_fire_intervals" );
}
namespace {
@@ -66,6 +101,44 @@ static void generic_print( F * f, lua_State * L ) {
f( str, len, fg, bg, bold );
}
+extern "C" int mud_connect( lua_State * L ) {
+ const char * host = luaL_checkstring( L, 1 );
+ int port = luaL_checkinteger( L, 2 );
+
+ const char * err;
+ void * sock = platform_connect( &err, host, port );
+ if( sock != NULL ) {
+ lua_pushlightuserdata( lua, sock );
+ return 1;
+ }
+
+ lua_pushnil( lua );
+ lua_pushstring( lua, err );
+
+ return 2;
+}
+
+extern "C" int mud_send( lua_State * L ) {
+ luaL_argcheck( L, lua_isuserdata( L, 1 ) == 1, 1, "expected socket" );
+ void * sock = lua_touserdata( L, 1 );
+
+ const char * data = luaL_checkstring( L, 2 );
+ size_t len = luaL_len( L, 2 );
+
+ platform_send( sock, data, len );
+
+ return 0;
+}
+
+extern "C" int mud_close( lua_State * L ) {
+ luaL_argcheck( L, lua_isuserdata( L, 1 ) == 1, 1, "expected socket" );
+ void * sock = lua_touserdata( L, 1 );
+
+ platform_close( sock );
+
+ return 0;
+}
+
extern "C" int mud_printMain( lua_State * L ) {
generic_print( ui_main_print, L );
return 0;
@@ -136,7 +209,11 @@ extern "C" int mud_setHandlers( lua_State * L ) {
luaL_argcheck( L, lua_type( L, 1 ) == LUA_TFUNCTION, 1, "expected function" );
luaL_argcheck( L, lua_type( L, 2 ) == LUA_TFUNCTION, 2, "expected function" );
luaL_argcheck( L, lua_type( L, 3 ) == LUA_TFUNCTION, 3, "expected function" );
+ luaL_argcheck( L, lua_type( L, 4 ) == LUA_TFUNCTION, 4, "expected function" );
+ luaL_argcheck( L, lua_type( L, 5 ) == LUA_TFUNCTION, 5, "expected function" );
+ intervalHandlerIdx = luaL_ref( L, LUA_REGISTRYINDEX );
+ socketHandlerIdx = luaL_ref( L, LUA_REGISTRYINDEX );
closeHandlerIdx = luaL_ref( L, LUA_REGISTRYINDEX );
macroHandlerIdx = luaL_ref( L, LUA_REGISTRYINDEX );
inputHandlerIdx = luaL_ref( L, LUA_REGISTRYINDEX );
@@ -149,6 +226,11 @@ extern "C" int mud_urgent( lua_State * L ) {
return 0;
}
+extern "C" int mud_now( lua_State * L ) {
+ lua_pushnumber( L, get_time() );
+ return 1;
+}
+
} // anon namespace
#if PLATFORM_WINDOWS
@@ -175,7 +257,6 @@ void script_init() {
exit( 1 );
}
- lua_pushinteger( lua, ui_display_fd() );
lua_pushcfunction( lua, mud_handleXEvents );
lua_pushcfunction( lua, mud_printMain );
@@ -192,10 +273,13 @@ void script_init() {
lua_pushcfunction( lua, mud_setStatus );
- if( lua_pcall( lua, 11, 0, -13 ) ) {
- printf( "Error running main.lua: %s\n", lua_tostring( lua, -1 ) );
- exit( 1 );
- }
+ lua_pushcfunction( lua, mud_connect );
+ lua_pushcfunction( lua, mud_send );
+ lua_pushcfunction( lua, mud_close );
+
+ lua_pushcfunction( lua, mud_now );
+
+ pcall( 14, "Error running main.lua" );
}
void script_term() {
diff --git a/src/script.h b/src/script.h
@@ -1,9 +1,13 @@
#pragma once
+#include <stddef.h>
+
// TODO: should be size_t here?
void script_handleInput( const char * buffer, int len );
void script_doMacro( const char * key, int len, bool shift, bool ctrl, bool alt );
void script_handleClose();
+void script_socketData( void * sock, const char * data, size_t len );
+void script_fire_intervals();
void script_init();
void script_term();
diff --git a/src/ui.h b/src/ui.h
@@ -39,13 +39,17 @@ void ui_chat_print( const char * str, size_t len, Colour fg, Colour bg, bool bol
void ui_fill_rect( int left, int top, int width, int height, Colour colour, bool bold );
void ui_draw_char( int left, int top, char c, Colour colour, bool bold, bool bold_font = false );
-void ui_dirty( int left, int top, int right, int bottom ); // TODO: x/y + w/h?
+void ui_dirty( int left, int top, int width, int height );
void ui_get_font_size( int * fw, int * fh );
void ui_urgent();
-int ui_display_fd(); // TODO: very x11 specific!
-
void ui_init();
void ui_term();
+
+void * platform_connect( const char ** err, const char * host, int port );
+void platform_send( void * sock, const char * data, size_t len );
+void platform_close( void * sock );
+
+void event_loop();
diff --git a/src/unix_network.cc b/src/unix_network.cc
@@ -0,0 +1,63 @@
+#include <arpa/inet.h>
+#include <sys/types.h>
+#include <sys/select.h>
+#include <sys/socket.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <unistd.h>
+
+#define INVALID_SOCKET ( -1 )
+#define SOCKET_ERROR ( -1 )
+
+#define closesocket close
+
+#if PLATFORM_OSX
+static int NET_SEND_FLAGS = 0;
+#else
+static int NET_SEND_FLAGS = MSG_NOSIGNAL;
+#endif
+
+void net_init() { }
+void net_term() { }
+
+static void make_socket_nonblocking( int fd ) {
+ int flags = fcntl( fd, F_GETFL, 0 );
+ if( flags == -1 ) FATAL( "fcntl F_GETFL" );
+ int ok = fcntl( fd, F_SETFL, flags | O_NONBLOCK );
+ if( ok == -1 ) FATAL( "fcntl F_SETFL" );
+}
+
+static void platform_init_sock( int fd ) {
+#if PLATFORM_OSX
+ setsockoptone( fd, SOL_SOCKET, SO_NOSIGPIPE );
+#endif
+}
+
+bool net_tryrecv( UDPSocket sock, void * buf, size_t len, NetAddress * addr, size_t * bytes_received ) {
+ struct sockaddr_storage sa;
+
+ socklen_t sa_size = sizeof( struct sockaddr_in );
+ ssize_t received4 = recvfrom( sock.ipv4, buf, len, 0, ( struct sockaddr * ) &sa, &sa_size );
+ if( received4 != -1 ) {
+ *addr = sockaddr_to_netaddress( sa );
+ *bytes_received = size_t( received4 );
+ return true;
+ }
+ if( received4 == -1 && errno != EAGAIN ) {
+ FATAL( "recvfrom" );
+ }
+
+ sa_size = sizeof( struct sockaddr_in6 );
+ ssize_t received6 = recvfrom( sock.ipv6, buf, len, 0, ( struct sockaddr * ) &sa, &sa_size );
+ if( received6 != -1 ) {
+ *addr = sockaddr_to_netaddress( sa );
+ *bytes_received = size_t( received6 );
+ return true;
+ }
+ if( received6 == -1 && errno != EAGAIN ) {
+ FATAL( "recvfrom" );
+ }
+
+ return false;
+}
diff --git a/src/unix_time.h b/src/unix_time.h
@@ -0,0 +1,10 @@
+#pragma once
+
+#include <time.h>
+
+inline double get_time() {
+ struct timespec ts;
+ clock_gettime( CLOCK_MONOTONIC, &ts );
+
+ return double( ts.tv_sec ) + ts.tv_nsec / 1e9;
+}
diff --git a/src/win32_network.cc b/src/win32_network.cc
@@ -0,0 +1,61 @@
+#include <winsock2.h>
+#include <ws2tcpip.h>
+
+static int NET_SEND_FLAGS = 0;
+
+void net_init() {
+ WSADATA wsa_data;
+ if( WSAStartup( MAKEWORD( 2, 2 ), &wsa_data ) == SOCKET_ERROR ) {
+ FATAL( "WSAStartup" );
+ }
+}
+
+void net_term() {
+ if( WSACleanup() == SOCKET_ERROR ) {
+ FATAL( "WSACleanup" );
+ }
+}
+
+static void make_socket_nonblocking( SOCKET fd ) {
+ u_long one = 1;
+ int ok = ioctlsocket( fd, FIONBIO, &one );
+ if( ok == SOCKET_ERROR ) {
+ FATAL( "ioctlsocket" );
+ }
+}
+
+static void platform_init_sock( SOCKET fd ) { }
+
+bool net_tryrecv( UDPSocket sock, void * buf, size_t len, NetAddress * addr, size_t * bytes_received ) {
+ struct sockaddr_storage sa;
+
+ socklen_t sa_size = sizeof( struct sockaddr_in );
+ ssize_t received4 = recvfrom( sock.ipv4, ( char * ) buf, len, 0, ( struct sockaddr * ) &sa, &sa_size );
+ if( received4 != SOCKET_ERROR ) {
+ *addr = sockaddr_to_netaddress( sa );
+ *bytes_received = size_t( received4 );
+ return true;
+ }
+ else {
+ int error = WSAGetLastError();
+ if( error != WSAEWOULDBLOCK && error != WSAECONNRESET ) {
+ FATAL( "recvfrom" );
+ }
+ }
+
+ sa_size = sizeof( struct sockaddr_in6 );
+ ssize_t received6 = recvfrom( sock.ipv6, ( char * ) buf, len, 0, ( struct sockaddr * ) &sa, &sa_size );
+ if( received6 != SOCKET_ERROR ) {
+ *addr = sockaddr_to_netaddress( sa );
+ *bytes_received = size_t( received6 );
+ return true;
+ }
+ else {
+ int error = WSAGetLastError();
+ if( error != WSAEWOULDBLOCK && error != WSAECONNRESET ) {
+ FATAL( "recvfrom" );
+ }
+ }
+
+ return false;
+}
diff --git a/src/win32_time.h b/src/win32_time.h
@@ -0,0 +1,13 @@
+#pragma once
+
+#include <windows.h>
+
+inline double get_time() {
+ LARGE_INTEGER counter;
+ QueryPerformanceCounter( &counter );
+
+ LARGE_INTEGER freq;
+ QueryPerformanceFrequency( &freq );
+
+ return double( counter.QuadPart ) / double( freq.QuadPart );
+}
diff --git a/src/x11.cc b/src/x11.cc
@@ -1,4 +1,5 @@
#include <err.h>
+#include <poll.h>
#include <X11/Xutil.h>
#include <X11/XKBlib.h>
@@ -10,6 +11,69 @@
#include "script.h"
#include "textbox.h"
+#include "platform_network.h"
+
+struct Socket {
+ TCPSocket sock;
+ bool in_use;
+};
+
+static Socket sockets[ 128 ];
+
+static bool closing = false;
+
+void * platform_connect( const char ** err, const char * host, int port ) {
+ size_t idx;
+ {
+ bool ok = false;
+ for( size_t i = 0; i < ARRAY_COUNT( sockets ); i++ ) {
+ if( !sockets[ i ].in_use ) {
+ idx = i;
+ ok = true;
+ break;
+ }
+ }
+
+ if( !ok ) {
+ *err = "too many connections";
+ return NULL;
+ }
+ }
+
+ NetAddress addr;
+ {
+ bool ok = dns_first( host, &addr );
+ if( !ok ) {
+ *err = "couldn't resolve hostname"; // TODO: error from dns_first
+ return NULL;
+ }
+ }
+ addr.port = checked_cast< u16 >( port );
+
+ TCPSocket sock;
+ bool ok = net_new_tcp( &sock, addr, NET_BLOCKING );
+ if( !ok ) {
+ *err = "net_new_tcp";
+ return NULL;
+ }
+
+ sockets[ idx ].sock = sock;
+ sockets[ idx ].in_use = true;
+
+ return &sockets[ idx ];
+}
+
+void platform_send( void * vsock, const char * data, size_t len ) {
+ Socket * sock = ( Socket * ) vsock;
+ net_send( sock->sock, data, len );
+}
+
+void platform_close( void * vsock ) {
+ Socket * sock = ( Socket * ) vsock;
+ net_destroy( &sock->sock );
+ sock->in_use = false;
+}
+
struct {
Display * display;
int screen;
@@ -461,6 +525,7 @@ static void event_mouse_move( XEvent * xevent ) {
static void event_message( XEvent * xevent ) {
if( ( Atom ) xevent->xclient.data.l[ 0 ] == wmDeleteWindow ) {
script_handleClose();
+ closing = true;
}
}
@@ -736,6 +801,10 @@ static void initStyle() {
}
void ui_init() {
+ for( Socket & s : sockets ) {
+ s.in_use = false;
+ }
+
UI = { };
textbox_init( &UI.main_text, SCROLLBACK_SIZE );
@@ -831,3 +900,64 @@ void ui_term() {
XDestroyWindow( UI.display, UI.window );
XCloseDisplay( UI.display );
}
+
+static Socket * socket_from_fd( int fd ) {
+ for( Socket & sock : sockets ) {
+ if( sock.in_use && sock.sock.fd == fd ) {
+ return &sock;
+ }
+ }
+
+ return NULL;
+}
+
+void event_loop() {
+ while( !closing ) {
+ pollfd fds[ ARRAY_COUNT( sockets ) + 1 ] = { };
+ nfds_t num_fds = 1;
+
+ fds[ 0 ].fd = ConnectionNumber( UI.display );
+ fds[ 0 ].events = POLLIN;
+
+ for( Socket sock : sockets ) {
+ if( sock.in_use ) {
+ fds[ num_fds ].fd = sock.sock.fd;
+ fds[ num_fds ].events = POLLIN;
+ num_fds++;
+ }
+ }
+
+ int ok = poll( fds, num_fds, 500 );
+ if( ok == -1 )
+ FATAL( "poll" );
+
+ if( ok == 0 ) {
+ script_fire_intervals();
+ continue;
+ }
+
+ for( size_t i = 1; i < ARRAY_COUNT( fds ); i++ ) {
+ if( fds[ i ].revents & POLLIN ) {
+ Socket * sock = socket_from_fd( fds[ i ].fd );
+ assert( sock != NULL );
+
+ char buf[ 8192 ];
+ size_t n;
+ TCPRecvResult res = net_recv( sock->sock, buf, sizeof( buf ), &n );
+ if( res == TCP_OK ) {
+ script_socketData( sock, buf, n );
+ }
+ else if( res == TCP_CLOSED ) {
+ script_socketData( sock, NULL, 0 );
+ }
+ else {
+ FATAL( "net_recv" );
+ }
+ }
+ }
+
+ if( fds[ 0 ].events & POLLIN ) {
+ ui_handleXEvents();
+ }
+ }
+}