├── .gitignore ├── DarkSideSync.sln ├── Specs ├── background worker lib spec 0v1.pdf ├── background worker lib spec 0v1.pptx ├── background worker lib spec 0v2.pptx ├── background worker lib spec 0v3.pptx ├── background worker lib spec 0v4.pdf └── background worker lib spec 0v4.pptx ├── clean.bat ├── config.ld ├── darksidesync-1.0-1.rockspec ├── darksidesync ├── .gitignore ├── darksidesync.c ├── darksidesync.geany ├── darksidesync.h ├── darksidesync.vcxproj ├── darksidesync.vcxproj.filters ├── darksidesync_api.h ├── darksidesync_aux.c ├── darksidesync_aux.h ├── debug.lua ├── delivery.c ├── delivery.h ├── dss.lua ├── dss_test.lua ├── locking.c ├── locking.h ├── locking_sequence.txt ├── udpsocket.c ├── udpsocket.h ├── waithandle.c └── waithandle.h ├── doc ├── background_worker_lib_spec_0v4.pdf ├── index.html ├── ldoc.css ├── modules │ ├── DSS.html │ └── darksidesync.html └── topics │ └── readme.md.html ├── generate_docs.bat ├── luaexit ├── .gitignore ├── install.bat ├── luaexit.c ├── luaexit.h ├── luaexit.vcxproj ├── luaexit.vcxproj.filters ├── luaexit_test.lua ├── luasignal.geany └── makefile └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | /DSS_VS/Debug 3 | /DSS_VS/luaexit/Debug 4 | /DSS_VS/darksidesync/Debug 5 | /Debug 6 | *.suo 7 | *.ncb 8 | /darksidesync/Debug 9 | /luaexit/Debug 10 | *.sdf 11 | /ipch 12 | /Release 13 | /darksidesync/Release 14 | /luaexit/Release 15 | 16 | /*.opensdf 17 | 18 | /darksidesync/*.o 19 | /darksidesync.dll 20 | -------------------------------------------------------------------------------- /DarkSideSync.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 11.00 3 | # Visual Studio 2010 4 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "luaexit", "luaexit\luaexit.vcxproj", "{18D94275-AC41-41EB-8CEA-96ED6050002D}" 5 | ProjectSection(ProjectDependencies) = postProject 6 | {FEDD20FF-D6EE-4B86-AB01-635CC9CDB6D7} = {FEDD20FF-D6EE-4B86-AB01-635CC9CDB6D7} 7 | EndProjectSection 8 | EndProject 9 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "darksidesync", "darksidesync\darksidesync.vcxproj", "{FEDD20FF-D6EE-4B86-AB01-635CC9CDB6D7}" 10 | EndProject 11 | Global 12 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 13 | Debug Lib|Win32 = Debug Lib|Win32 14 | Debug Lib|x64 = Debug Lib|x64 15 | Debug|Win32 = Debug|Win32 16 | Debug|x64 = Debug|x64 17 | Release Lib|Win32 = Release Lib|Win32 18 | Release Lib|x64 = Release Lib|x64 19 | Release|Win32 = Release|Win32 20 | Release|x64 = Release|x64 21 | EndGlobalSection 22 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 23 | {18D94275-AC41-41EB-8CEA-96ED6050002D}.Debug Lib|Win32.ActiveCfg = Debug|Win32 24 | {18D94275-AC41-41EB-8CEA-96ED6050002D}.Debug Lib|Win32.Build.0 = Debug|Win32 25 | {18D94275-AC41-41EB-8CEA-96ED6050002D}.Debug Lib|x64.ActiveCfg = Debug|Win32 26 | {18D94275-AC41-41EB-8CEA-96ED6050002D}.Debug|Win32.ActiveCfg = Debug|Win32 27 | {18D94275-AC41-41EB-8CEA-96ED6050002D}.Debug|Win32.Build.0 = Debug|Win32 28 | {18D94275-AC41-41EB-8CEA-96ED6050002D}.Debug|x64.ActiveCfg = Debug|Win32 29 | {18D94275-AC41-41EB-8CEA-96ED6050002D}.Release Lib|Win32.ActiveCfg = Release|Win32 30 | {18D94275-AC41-41EB-8CEA-96ED6050002D}.Release Lib|Win32.Build.0 = Release|Win32 31 | {18D94275-AC41-41EB-8CEA-96ED6050002D}.Release Lib|x64.ActiveCfg = Release|Win32 32 | {18D94275-AC41-41EB-8CEA-96ED6050002D}.Release|Win32.ActiveCfg = Release|Win32 33 | {18D94275-AC41-41EB-8CEA-96ED6050002D}.Release|Win32.Build.0 = Release|Win32 34 | {18D94275-AC41-41EB-8CEA-96ED6050002D}.Release|x64.ActiveCfg = Release|Win32 35 | {FEDD20FF-D6EE-4B86-AB01-635CC9CDB6D7}.Debug Lib|Win32.ActiveCfg = Debug|Win32 36 | {FEDD20FF-D6EE-4B86-AB01-635CC9CDB6D7}.Debug Lib|Win32.Build.0 = Debug|Win32 37 | {FEDD20FF-D6EE-4B86-AB01-635CC9CDB6D7}.Debug Lib|x64.ActiveCfg = Debug|Win32 38 | {FEDD20FF-D6EE-4B86-AB01-635CC9CDB6D7}.Debug|Win32.ActiveCfg = Debug|Win32 39 | {FEDD20FF-D6EE-4B86-AB01-635CC9CDB6D7}.Debug|Win32.Build.0 = Debug|Win32 40 | {FEDD20FF-D6EE-4B86-AB01-635CC9CDB6D7}.Debug|x64.ActiveCfg = Debug|Win32 41 | {FEDD20FF-D6EE-4B86-AB01-635CC9CDB6D7}.Release Lib|Win32.ActiveCfg = Release|Win32 42 | {FEDD20FF-D6EE-4B86-AB01-635CC9CDB6D7}.Release Lib|Win32.Build.0 = Release|Win32 43 | {FEDD20FF-D6EE-4B86-AB01-635CC9CDB6D7}.Release Lib|x64.ActiveCfg = Release|Win32 44 | {FEDD20FF-D6EE-4B86-AB01-635CC9CDB6D7}.Release|Win32.ActiveCfg = Release|Win32 45 | {FEDD20FF-D6EE-4B86-AB01-635CC9CDB6D7}.Release|Win32.Build.0 = Release|Win32 46 | {FEDD20FF-D6EE-4B86-AB01-635CC9CDB6D7}.Release|x64.ActiveCfg = Release|Win32 47 | EndGlobalSection 48 | GlobalSection(SolutionProperties) = preSolution 49 | HideSolutionNode = FALSE 50 | EndGlobalSection 51 | EndGlobal 52 | -------------------------------------------------------------------------------- /Specs/background worker lib spec 0v1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tieske/DarkSideSync/060cfc55774ea0a5b42361b87b2ec7a3ff2ea7e2/Specs/background worker lib spec 0v1.pdf -------------------------------------------------------------------------------- /Specs/background worker lib spec 0v1.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tieske/DarkSideSync/060cfc55774ea0a5b42361b87b2ec7a3ff2ea7e2/Specs/background worker lib spec 0v1.pptx -------------------------------------------------------------------------------- /Specs/background worker lib spec 0v2.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tieske/DarkSideSync/060cfc55774ea0a5b42361b87b2ec7a3ff2ea7e2/Specs/background worker lib spec 0v2.pptx -------------------------------------------------------------------------------- /Specs/background worker lib spec 0v3.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tieske/DarkSideSync/060cfc55774ea0a5b42361b87b2ec7a3ff2ea7e2/Specs/background worker lib spec 0v3.pptx -------------------------------------------------------------------------------- /Specs/background worker lib spec 0v4.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tieske/DarkSideSync/060cfc55774ea0a5b42361b87b2ec7a3ff2ea7e2/Specs/background worker lib spec 0v4.pdf -------------------------------------------------------------------------------- /Specs/background worker lib spec 0v4.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tieske/DarkSideSync/060cfc55774ea0a5b42361b87b2ec7a3ff2ea7e2/Specs/background worker lib spec 0v4.pptx -------------------------------------------------------------------------------- /clean.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | SETLOCAL 3 | SET SPATH=%~dp0 4 | @ECHO ON 5 | rd /S /Q "%SPATH%Debug" 6 | rd /S /Q "%SPATH%ipch" 7 | rd /S /Q "%SPATH%Release" 8 | rd /S /Q "%SPATH%luaexit\Debug" 9 | rd /S /Q "%SPATH%luaexit\Release" 10 | rd /S /Q "%SPATH%darksidesync\Debug" 11 | rd /S /Q "%SPATH%darksidesync\Release" 12 | del /S /Q "%SPATH%darksidesync\*.o" 13 | @ECHO OFF 14 | pause 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /config.ld: -------------------------------------------------------------------------------- 1 | project='DarkSideSync' 2 | title='DarkSideSync reference' 3 | description='Background thread synchronization module for Lua' 4 | format='discount' 5 | file= { 6 | "darksidesync/dss.lua", 7 | "darksidesync/darksidesync.c", 8 | } 9 | dir='doc' 10 | readme='readme.md' 11 | -------------------------------------------------------------------------------- /darksidesync-1.0-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "darksidesync" 2 | version = "1.0-1" 3 | source = { 4 | url = "https://github.com/Tieske/DarkSideSync/archive/version_1.0.tar.gz", 5 | dir = "DarkSideSync-version_1.0", 6 | } 7 | description = { 8 | summary = "Thread synchronization support for bindings to libraries with their own threadpools", 9 | detailed = [[ 10 | DarkSideSync is a binding support library that makes it easy to create 11 | bindings to libraries that run their own background threads, like pupnp 12 | or OpenZwave for example. No global locks are required and no foreign 13 | threads will be entering the Lua environment. Bindings using DarkSideSync 14 | will not require platform specific code for synchronization. 15 | ]], 16 | homepage = "https://github.com/Tieske/DarkSideSync", 17 | license = "MIT" 18 | } 19 | dependencies = { 20 | "lua >= 5.1, < 5.2" 21 | } 22 | build = { 23 | type = "builtin", 24 | platforms = { 25 | unix = { 26 | modules = { 27 | ["darksidesync"] = { 28 | sources = { 29 | "darksidesync/darksidesync.c", 30 | "darksidesync/delivery.c", 31 | "darksidesync/locking.c", 32 | "darksidesync/udpsocket.c", 33 | "darksidesync/waithandle.c", 34 | }, 35 | libraries = { 36 | "pthread" 37 | }, 38 | defines = { 39 | "_GNU_SOURCE", 40 | } 41 | } 42 | } 43 | }, 44 | win32 = { 45 | modules = { 46 | ["darksidesync"] = { 47 | sources = { 48 | "darksidesync/darksidesync.c", 49 | "darksidesync/delivery.c", 50 | "darksidesync/locking.c", 51 | "darksidesync/udpsocket.c", 52 | "darksidesync/waithandle.c", 53 | }, 54 | libraries = { 55 | "wsock32" 56 | }, 57 | defines = { 58 | } 59 | } 60 | } 61 | } 62 | }, 63 | modules = { 64 | ["dss"] = "darksidesync/dss.lua", 65 | }, 66 | } 67 | -------------------------------------------------------------------------------- /darksidesync/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | *.user -------------------------------------------------------------------------------- /darksidesync/darksidesync.c: -------------------------------------------------------------------------------- 1 | // This file has documented functions for the Lua side functions 2 | // documentation can be generated with LDoc 3 | // Documentation comments will start with /*** and end with */ 4 | 5 | /*** 6 | DarkSideSync is a Lua helper module for asynchroneous callbacks from 7 | other libraries. Lua is single-threaded by nature and hence working with 8 | multithreaded libraries is a complex matter. DarkSideSync aim is to make 9 | using asynchroneous libraries (managing their own threadpools) simple. 10 | 11 | DarkSideSync takes away the complexity of messages queues, 12 | locking, synchronization, etc. because it implements them once and has a 13 | thread safe API to perform all those tasks, and notify Lua of incoming 14 | threads/data. It is a regular Lua module (C library) that can be loaded 15 | from Lua (no C-side dependencies/linking for any libraries using DarkSideSync) 16 | and it supports many libraries to consume its services simultaneously. 17 | 18 | [Check here for an overview](../dss_overview.htm). 19 | 20 | It can only work with libraries designed to work with DarkSideSync. Check 21 | out the library source, specifically [`darksidesync_api.h`](https://github.com/Tieske/DarkSideSync/blob/master/darksidesync/darksidesync_api.h) on how 22 | to do that. Additionally use [`darksidesync_aux.c`](https://github.com/Tieske/DarkSideSync/blob/master/darksidesync/darksidesync_aux.c) to get up and 23 | running with DarkSideSync quickly (just an include of this file will get 24 | you 95% done). 25 | 26 | To use the DarkSideSync library from Lua there are 2 options 27 | 28 | 1. do not use notifications, but regularly call `poll` to check for incoming data 29 | 1. use the UDP notification mechanism (a LuaSocket implementation is available in the `dss` module). 30 | 31 | The latter has UDP networking overhead but has some advantages; works with any network library and 32 | allows the application to 'go to sleep' in a network `select()` call. Additionally a UDP socket 33 | has the advantage (over a filehandle) that it works on (almost) every platform. 34 | In cases with a high number of callbacks the polling method is considered the better solution. 35 | 36 | If you'll be using LuaSocket, then you can probably use the `dss` module which has a LuaSocket specific 37 | abstraction on top of this darksidesync core module. 38 | 39 | @class module 40 | @name darksidesync 41 | @copyright 2012-2013 Thijs Schreijer, DarkSideSync is free software under the MIT/X11 license 42 | @release Version 1.0, DarkSideSync. 43 | 44 | */ 45 | 46 | /*** 47 | C-side DarkSideSync API. 48 | This section covers the darksidesync API from the C-side. It is not separately documented, but only in 49 | the code files. 50 | @section C-API 51 | */ 52 | 53 | /*** 54 | Contains the complete C-side API. 55 | See API header file [darksidesync\_api.h](https://github.com/Tieske/DarkSideSync/blob/master/darksidesync/darksidesync_api.h). 56 | @function darksidesync_api.h 57 | */ 58 | 59 | /*** 60 | Contains the core client implementation code. 61 | An implementation of the C-side API ([darksidesync\_aux.c](https://github.com/Tieske/DarkSideSync/blob/master/darksidesync/darksidesync_aux.c) 62 | and [darksidesync\_aux.h](https://github.com/Tieske/DarkSideSync/blob/master/darksidesync/darksidesync_aux.h)) is available. 63 | This implementation should suffice for most usecases. Just copy the file into your project and include it (make sure to read the notes on linking 64 | in `darksidesync_api.h`). 65 | @function darksidesync_aux.c 66 | */ 67 | 68 | /*** 69 | Lua-side DarkSideSync API. 70 | This section covers the darksidesync API from the Lua-side 71 | @section Lua-API 72 | */ 73 | 74 | #include 75 | #include 76 | #include "udpsocket.h" 77 | #include "locking.h" 78 | #include "delivery.h" 79 | #include "darksidesync.h" 80 | 81 | static putilRecord volatile UtilStart = NULL; // Holds first utility in the list 82 | static void* volatile DSS_initialized = NULL; // while its NULL, the first mutex is uninitialized 83 | static DSS_mutex_t dsslock; // lock for all shared DSS access 84 | static int statecount = 0; // counter for number of lua states using this lib 85 | //static DSS_mutex_t statelock; // lock to protect the state counter 86 | static DSS_api_1v0_t DSS_api_1v0; // API struct for version 1.0 87 | 88 | // forward definitions 89 | static void setUDPPort (pglobalRecord g, int newPort); 90 | 91 | #ifdef _DEBUG 92 | //can be found here http://www.lua.org/pil/24.2.3.html 93 | static void stackDump (lua_State *L, const char *text) { 94 | int i; 95 | int top = lua_gettop(L); 96 | if (text == NULL) 97 | printf("--------Start Dump------------\n"); 98 | else 99 | printf("--------Start %s------------\n", text); 100 | for (i = 1; i <= top; i++) { /* repeat for each level */ 101 | int t = lua_type(L, i); 102 | switch (t) { 103 | 104 | case LUA_TSTRING: /* strings */ 105 | printf("`%s'", lua_tostring(L, i)); 106 | break; 107 | 108 | case LUA_TBOOLEAN: /* booleans */ 109 | printf(lua_toboolean(L, i) ? "true" : "false"); 110 | break; 111 | 112 | case LUA_TNUMBER: /* numbers */ 113 | printf("%g", lua_tonumber(L, i)); 114 | break; 115 | 116 | default: /* other values */ 117 | printf("%s", lua_typename(L, t)); 118 | break; 119 | 120 | } 121 | printf(" "); /* put a separator */ 122 | } 123 | printf("\n"); /* end the listing */ 124 | printf("--------End Dump------------\n"); 125 | } 126 | #endif 127 | 128 | /* 129 | ** =============================================================== 130 | ** Utility and globals registration functions 131 | ** =============================================================== 132 | */ 133 | // Returns 1 if the LuaState has a global struct, 0 otherwise 134 | static int DSS_hasstateglobals(lua_State *L) 135 | { 136 | pglobalRecord g; 137 | // try and collect the globals userdata 138 | lua_getfield(L, LUA_REGISTRYINDEX, DSS_GLOBALS_KEY); 139 | g = (pglobalRecord)lua_touserdata(L, -1); 140 | lua_pop(L,1); 141 | if (g == NULL) return 0; 142 | return 1; 143 | } 144 | 145 | // Creates a new global record and stores it in the registry 146 | // will overwrite existing if present!! 147 | // Returns: created globalrecord or NULL on failure 148 | // provide errno may be NULL; possible errcode; 149 | // DSS_SUCCESS, DSS_ERR_OUT_OF_MEMORY, DSS_ERR_NO_GLOBALS 150 | static pglobalRecord DSS_newstateglobals(lua_State *L, int* errcode) 151 | { 152 | pglobalRecord g; 153 | 154 | int le; // local errorcode 155 | if (errcode == NULL) errcode = ≤ 156 | *errcode = DSS_SUCCESS; 157 | 158 | // create a new one 159 | g = (pglobalRecord)lua_newuserdata(L, sizeof(globalRecord)); 160 | if (g == NULL) *errcode = DSS_ERR_OUT_OF_MEMORY; // alloc failed 161 | 162 | if (*errcode == DSS_SUCCESS) 163 | { 164 | // now setup UDP port and status 165 | g->udpport = 0; 166 | g->socket = udpsocket_new(g->udpport); 167 | g->DSS_status = DSS_STATUS_STOPPED; 168 | 169 | // setup data queue 170 | //if (DSS_mutexInitx(&(g->lock)) != 0) *errcode = DSS_ERR_INIT_MUTEX_FAILED; 171 | } 172 | 173 | if (*errcode == DSS_SUCCESS) 174 | { 175 | g->QueueCount = 0; 176 | g->QueueEnd = NULL; 177 | g->QueueStart = NULL; 178 | g->UserdataStart = NULL; 179 | } 180 | 181 | if (*errcode != DSS_SUCCESS) // we had an error 182 | { 183 | // report error, errcode contains details 184 | return NULL; 185 | } 186 | 187 | // now add a garbagecollect metamethod and anchor it 188 | luaL_getmetatable(L, DSS_GLOBALS_MT); // get metatable with GC method 189 | lua_setmetatable(L, -2); // set it to the created userdata 190 | lua_setfield(L, LUA_REGISTRYINDEX, DSS_GLOBALS_KEY); // anchor the userdata 191 | 192 | return g; 193 | } 194 | 195 | // Gets the pointer to the structure with all LuaState related g 196 | // that are to be kept outside the LuaState (to be accessible from the 197 | // async callbacks) 198 | // Locking isn't necessary, as the structure is collected from/added to 199 | // the Lua_State, hence the calling code must already be single threaded. 200 | // returns NULL upon failure, use errcode for details. 201 | // provided errno may be NULL; possible errcode; 202 | // DSS_SUCCESS, DSS_ERR_NO_GLOBALS 203 | // see also DSS_getvalidglobals() 204 | static pglobalRecord DSS_getstateglobals(lua_State *L, int* errcode) 205 | { 206 | 207 | pglobalRecord g; 208 | 209 | int le; // local errorcode 210 | if (errcode == NULL) errcode = ≤ 211 | *errcode = DSS_SUCCESS; 212 | 213 | // try and collect the globals userdata 214 | lua_getfield(L, LUA_REGISTRYINDEX, DSS_GLOBALS_KEY); 215 | g = (pglobalRecord)lua_touserdata(L, -1); 216 | lua_pop(L,1); 217 | 218 | if (g == NULL) *errcode = DSS_ERR_NO_GLOBALS; 219 | 220 | return g; 221 | } 222 | 223 | // checks global struct, returns 1 if valid, 0 otherwise 224 | // Caller should lock global record!! 225 | static int DSS_isvalidglobals(pglobalRecord g) 226 | { 227 | int result = 1; // assume valid 228 | if (g == NULL) // no use, caller should have locked, hence have checked NULL .... 229 | { 230 | result = 0; // not valid 231 | } 232 | else 233 | { 234 | if (g->DSS_status != DSS_STATUS_STARTED) result = 0; // not valid 235 | } 236 | return result; 237 | } 238 | 239 | // returns a valid globals struct or fails with a Lua error (and won't 240 | // return in that case) 241 | static pglobalRecord DSS_getvalidglobals(lua_State *L) 242 | { 243 | pglobalRecord g = DSS_getstateglobals(L, NULL); // no errorcode required 244 | if (g == NULL || DSS_isvalidglobals(g) == 0) 245 | { 246 | // following error call will not return 247 | luaL_error(L, "DSS was not started yet, or already stopped"); 248 | } 249 | return g; 250 | } 251 | 252 | // Garbage collect function for the global userdata 253 | // DSS is exiting from this LuaState, so clean it all up 254 | static int DSS_clearstateglobals(lua_State *L) 255 | { 256 | pglobalRecord g; 257 | putilRecord listend; 258 | 259 | DSS_mutex_lock(&dsslock); 260 | g = (pglobalRecord)lua_touserdata(L, 1); // first param is userdata to destroy 261 | 262 | #ifdef _DEBUG 263 | OutputDebugStringA("DSS: Unloading DSS ...\n"); 264 | #endif 265 | // Set status to stopping, registering and delivering will fail from here on 266 | g->DSS_status = DSS_STATUS_STOPPING; 267 | 268 | // cancel all utilities, in reverse order 269 | while (UtilStart != NULL) 270 | { 271 | listend = UtilStart; 272 | while (listend->pNext != NULL) listend = listend->pNext; 273 | 274 | DSS_mutex_unlock(&dsslock); // must unlock to let the cancel function succeed 275 | listend->pCancel(listend); // call this utility's cancel method 276 | DSS_mutex_lock(&dsslock); // lock again to get the next one 277 | } 278 | 279 | // update status again, we're done stopping 280 | g->DSS_status = DSS_STATUS_STOPPED; 281 | 282 | // Remove references from the registry 283 | lua_pushnil(L); 284 | lua_setfield(L, LUA_REGISTRYINDEX, DSS_REGISTRY_NAME); 285 | 286 | // Close socket and destroy mutex 287 | setUDPPort(g, 0); // set port to 0, will close socket 288 | 289 | // Reduce state count and close network if none left 290 | statecount = statecount - 1; 291 | if (statecount == 0) 292 | { 293 | udpsocket_networkStop(); 294 | } 295 | DSS_mutex_unlock(&dsslock); 296 | #ifdef _DEBUG 297 | OutputDebugStringA("DSS: Unloading DSS completed\n"); 298 | #endif 299 | return 0; 300 | } 301 | 302 | /* 303 | ** =============================================================== 304 | ** Queue management functions 305 | ** =============================================================== 306 | */ 307 | 308 | // Pop item from the queue 309 | // Returns queueItem, or NULL if none available 310 | // utilid is id of utility for which to return, or NULL to 311 | // just return the oldest item, independent of a utilid 312 | static pQueueItem queuePopUnlocked(pglobalRecord g, putilRecord utilid) 313 | { 314 | pQueueItem qi = NULL; 315 | 316 | if (g->QueueStart != NULL) 317 | { 318 | 319 | if (utilid == NULL) 320 | { 321 | // just grab the oldest from the queue 322 | qi = g->QueueStart; 323 | } 324 | else 325 | { 326 | // traverse backwards to find the last one with a corresponding utilid 327 | qi = g->QueueStart; 328 | while (qi != NULL && qi->utilid != utilid) 329 | { 330 | qi = qi->pPrevious; 331 | } 332 | } 333 | 334 | if (qi != NULL) 335 | { 336 | // dismiss item from linked list 337 | if (qi == g->QueueStart) g->QueueStart = qi->pNext; 338 | if (qi == g->QueueEnd) g->QueueEnd = qi->pPrevious; 339 | if (qi->pPrevious != NULL) qi->pPrevious->pNext = qi->pNext; 340 | if (qi->pNext != NULL) qi->pNext->pPrevious = qi->pPrevious; 341 | // cleanup results 342 | qi->pNext = NULL; 343 | qi->pPrevious = NULL; 344 | g->QueueCount -= 1; 345 | } 346 | } 347 | 348 | return qi; //result; 349 | } 350 | 351 | 352 | /* 353 | ** =============================================================== 354 | ** UDP socket management functions 355 | ** =============================================================== 356 | */ 357 | // Changes the UDP port number in use 358 | // uses the lock on the globals, will only be called from Lua 359 | static void setUDPPort (pglobalRecord g, int newPort) 360 | { 361 | if (g->udpport != 0) 362 | { 363 | udpsocket_close(g->socket); 364 | } 365 | g->udpport = newPort; 366 | if (newPort != 0) 367 | { 368 | g->socket = udpsocket_new(newPort); 369 | } 370 | } 371 | 372 | /* 373 | ** =============================================================== 374 | ** C API 375 | ** =============================================================== 376 | */ 377 | // check utildid against list, 1 if it exists, 0 if not 378 | static int DSS_validutil(putilRecord utilid) 379 | { 380 | putilRecord id = UtilStart; // start at top 381 | if (utilid != id) 382 | { 383 | while ((id != NULL) && (id->pNext != utilid)) id = id->pNext; 384 | if (id == NULL) return 0; // not found 385 | } 386 | return 1; // found it 387 | } 388 | 389 | // Call this to deliver data to the queue 390 | // @returns; DSS_SUCCESS, DSS_ERR_UDP_SEND_FAILED, 391 | // DSS_ERR_OUT_OF_MEMORY, DSS_ERR_NOT_STARTED, DSS_ERR_INVALID_UTILID 392 | static int DSS_deliver_1v0 (putilRecord utilid, DSS_decoder_1v0_t pDecode, DSS_return_1v0_t pReturn, void* pData) 393 | { 394 | pglobalRecord g; 395 | int result = DSS_SUCCESS; // report success by default 396 | pQueueItem pqi; 397 | pDSS_waithandle wh; 398 | 399 | #ifdef _DEBUG 400 | OutputDebugStringA("DSS: Start delivering data ...\n"); 401 | #endif 402 | 403 | DSS_mutex_lock(&dsslock); 404 | if (DSS_validutil(utilid) == 0) 405 | { 406 | // invalid ID 407 | DSS_mutex_unlock(&dsslock); 408 | return DSS_ERR_INVALID_UTILID; 409 | } 410 | 411 | if (pDecode == NULL) 412 | { 413 | // No decode callback provided 414 | DSS_mutex_unlock(&dsslock); 415 | return DSS_ERR_NO_DECODE_PROVIDED; 416 | } 417 | 418 | g = utilid->pGlobals; 419 | if (g->DSS_status != DSS_STATUS_STARTED) 420 | { 421 | // lib not started yet (or stopped already), exit 422 | DSS_mutex_unlock(&dsslock); 423 | return DSS_ERR_NOT_STARTED; 424 | } 425 | 426 | // Go and deliver it 427 | pqi = delivery_new(utilid, pDecode, pReturn, pData, &result); 428 | 429 | // get waithandle and unlock to let the delivery be processed 430 | wh = pqi->pWaitHandle; 431 | pqi = NULL; // let go here, after the lock is released, we can no longer assume it valid 432 | DSS_mutex_unlock(&dsslock); 433 | 434 | if (wh != NULL) 435 | { 436 | // A waithandle was created, so we must go and wait for the queued item to be completed 437 | DSS_waithandle_wait(wh); // blocks until released 438 | DSS_waithandle_delete(wh); // destroy waithandle 439 | } 440 | 441 | #ifdef _DEBUG 442 | OutputDebugStringA("DSS: End delivering data ...\n"); 443 | #endif 444 | return result; 445 | }; 446 | 447 | // Gets the utilid based on a LuaState and libid 448 | // return NULL upon failure, see Errcode for details; DSS_SUCCESS, 449 | // DSS_ERR_NOT_STARTED or DSS_ERR_UNKNOWN_LIB 450 | static void* DSS_getutilid_1v0(lua_State *L, void* libid, int* errcode) 451 | { 452 | pglobalRecord g = DSS_getstateglobals(L, NULL); 453 | putilRecord utilid = NULL; 454 | 455 | int le; // local errorcode 456 | if (errcode == NULL) errcode = ≤ 457 | *errcode = DSS_SUCCESS; 458 | 459 | if (g != NULL) 460 | { 461 | DSS_mutex_lock(&dsslock); 462 | if (g->DSS_status != DSS_STATUS_STARTED) 463 | { 464 | DSS_mutex_unlock(&dsslock); 465 | *errcode = DSS_ERR_NOT_STARTED; 466 | return NULL; 467 | } 468 | 469 | // we've got a set of globals, now compare this to the utillist 470 | utilid = UtilStart; 471 | while (utilid != NULL) 472 | { 473 | if ((utilid->pGlobals == g) && (utilid->libid == libid)) 474 | { 475 | //This utilid matches both the LuaState (globals) and the libid. 476 | DSS_mutex_unlock(&dsslock); 477 | return utilid; // found it, return and exit. 478 | } 479 | 480 | // No match, try next one in the list 481 | utilid = utilid->pNext; 482 | } 483 | DSS_mutex_unlock(&dsslock); 484 | } 485 | else 486 | { 487 | // No Globals found 488 | *errcode = DSS_ERR_NOT_STARTED; 489 | return NULL; 490 | } 491 | // we had globals, failed anyway, so no valid utilid 492 | *errcode = DSS_ERR_UNKNOWN_LIB; 493 | return NULL; // failed 494 | } 495 | 496 | // register a library to use DSS 497 | // @arg1; the globals record the utility is added to 498 | // @arg2; pointer to the cancel method of the utility, will be called 499 | // @arg3; integer to take the errorcode to return 500 | // when DSS decides to terminate the collaboration with the utility 501 | // Returns: unique ID for the utility that must be used for all subsequent 502 | // calls to DSS, or NULL if it failed. 503 | // Failure reasons; DSS_ERR_NOT_STARTED, DSS_ERR_NO_CANCEL_PROVIDED, 504 | // DSS_ERR_ALREADY_REGISTERED, DSS_ERR_OUT_OF_MEMORY 505 | // NOTE: if the lib was already registered, it will return the existing ID, 506 | // but it will ignore all provided parameters (nothing will be changed) 507 | static putilRecord DSS_register_1v0(lua_State *L, void* libid, DSS_cancel_1v0_t pCancel, int* errcode) 508 | { 509 | putilRecord util; 510 | putilRecord last; 511 | pglobalRecord g; 512 | 513 | int le; // local errorcode 514 | if (errcode == NULL) errcode = ≤ 515 | *errcode = DSS_SUCCESS; 516 | 517 | #ifdef _DEBUG 518 | OutputDebugStringA("DSS: Start registering lib ...\n"); 519 | #endif 520 | if (pCancel == NULL) 521 | { 522 | *errcode = DSS_ERR_NO_CANCEL_PROVIDED; 523 | return NULL; 524 | } 525 | 526 | util = (putilRecord)DSS_getutilid_1v0(L, libid, NULL); 527 | if (util != NULL) 528 | { 529 | // We got an ID returned, so this lib is already registered 530 | *errcode = DSS_ERR_ALREADY_REGISTERED; 531 | return util; // don't change anything, but return existing id 532 | } 533 | 534 | DSS_mutex_lock(&dsslock); 535 | g = DSS_getstateglobals(L, NULL); 536 | if (g == NULL) 537 | { 538 | *errcode = DSS_ERR_NOT_STARTED; 539 | DSS_mutex_unlock(&dsslock); 540 | return NULL; 541 | } 542 | 543 | if (g->DSS_status != DSS_STATUS_STARTED) 544 | { 545 | // DSS isn't running 546 | *errcode = DSS_ERR_NOT_STARTED; 547 | DSS_mutex_unlock(&dsslock); 548 | return NULL; 549 | } 550 | 551 | // create and fill utility record 552 | util = (putilRecord)malloc(sizeof(utilRecord)); 553 | if (util == NULL) 554 | { 555 | DSS_mutex_unlock(&dsslock); 556 | *errcode = DSS_ERR_OUT_OF_MEMORY; 557 | return NULL; 558 | } 559 | util->pCancel = pCancel; 560 | util->pGlobals = g; 561 | util->libid = libid; 562 | util->pNext = NULL; 563 | util->pPrevious = NULL; 564 | 565 | // Add record to end of list 566 | last = UtilStart; 567 | while (last != NULL && last->pNext != NULL) last = last->pNext; 568 | if (last == NULL) 569 | { 570 | // first item 571 | UtilStart = util; 572 | } 573 | else 574 | { 575 | // append to list 576 | last->pNext = util; 577 | util->pPrevious = last; 578 | } 579 | 580 | DSS_mutex_unlock(&dsslock); 581 | #ifdef _DEBUG 582 | OutputDebugStringA("DSS: Done registering lib ...\n"); 583 | #endif 584 | return util; 585 | } 586 | 587 | 588 | // unregisters a previously registered utility 589 | // cancels all items still in queue 590 | // returns DSS_SUCCESS, DSS_ERR_INVALID_UTILID 591 | static int DSS_unregister_1v0(putilRecord utilid) 592 | { 593 | pglobalRecord g; 594 | //pQueueItem nqi = NULL; 595 | pQueueItem pqi = NULL; 596 | 597 | #ifdef _DEBUG 598 | OutputDebugStringA("DSS: Start unregistering lib ...\n"); 599 | #endif 600 | 601 | DSS_mutex_lock(&dsslock); 602 | 603 | if (DSS_validutil(utilid) == 0) 604 | { 605 | // invalid ID 606 | DSS_mutex_unlock(&dsslock); 607 | return DSS_ERR_INVALID_UTILID; 608 | } 609 | g = utilid->pGlobals; 610 | 611 | // remove it from the list 612 | if (UtilStart == utilid) UtilStart = utilid->pNext; 613 | if (utilid->pNext != NULL) utilid->pNext->pPrevious = utilid->pPrevious; 614 | if (utilid->pPrevious != NULL) utilid->pPrevious->pNext = utilid->pNext; 615 | 616 | // Cancel all items stored in userdatas 617 | pqi = g->UserdataStart; 618 | while (pqi != NULL) 619 | { 620 | if (pqi->utilid == utilid) 621 | { 622 | // need to cancel this one, as it has our ID 623 | delivery_cancel(pqi); 624 | // restart as list has been modified... 625 | pqi = g->UserdataStart; 626 | } 627 | else 628 | { 629 | // move to next item in list 630 | pqi = pqi->pNext; 631 | } 632 | } 633 | 634 | // cancel all items still in the queue 635 | pqi = g->QueueEnd; 636 | while (pqi != NULL) 637 | { 638 | if (pqi->utilid == utilid) 639 | { 640 | // need to cancel this one, as it has our ID 641 | delivery_cancel(pqi); 642 | // restart as list has been modified... 643 | pqi = g->QueueEnd; 644 | } 645 | else 646 | { 647 | // move to next item in list 648 | pqi = pqi->pPrevious; 649 | } 650 | } 651 | 652 | // free resources 653 | free(utilid); 654 | 655 | // Unlock, we're done with the util list 656 | DSS_mutex_unlock(&dsslock); 657 | #ifdef _DEBUG 658 | OutputDebugStringA("DSS: Done unregistering lib ...\n"); 659 | #endif 660 | return DSS_SUCCESS; 661 | } 662 | 663 | /* 664 | ** =============================================================== 665 | ** Lua API 666 | ** =============================================================== 667 | */ 668 | /*** 669 | Sets the UDP port for notifications. For every item delivered in the 670 | darksidesync queue a notification will be sent. The IP address the notification 671 | will be send to will always be `localhost` (loopback adapter). 672 | @function setport 673 | @param port UDP port number to use for notification packets. A value from 0 to 65535, where 0 will disable notifications. 674 | @return 1 if successfull, or `nil + error msg` if it failed 675 | @see getport 676 | */ 677 | static int L_setport(lua_State *L) 678 | { 679 | if (lua_gettop(L) >= 1 && luaL_checkint(L,1) >= 0 && luaL_checkint(L,1) <= 65535) 680 | { 681 | pglobalRecord g = DSS_getvalidglobals(L); // won't return on error 682 | DSS_mutex_lock(&dsslock); 683 | setUDPPort(g, luaL_checkint(L,1)); 684 | DSS_mutex_unlock(&dsslock); 685 | // report success 686 | lua_pushinteger(L, 1); 687 | return 1; 688 | } 689 | else 690 | { 691 | // There are no parameters, or the first isn't a number, call will not return 692 | return luaL_error(L, "Invalid UDP port number, use an integer value from 1 to 65535, or 0 to disable UDP notification"); 693 | } 694 | }; 695 | 696 | /*** 697 | Returns the UDP port currently in use for notifications. 698 | @function getport 699 | @return UDP portnumber in use (1-65535), or 0 if notifications are disabled 700 | @see setport 701 | */ 702 | static int L_getport (lua_State *L) 703 | { 704 | pglobalRecord g = DSS_getvalidglobals(L); // won't return on error 705 | DSS_mutex_lock(&dsslock); 706 | lua_pushinteger(L, g->udpport); 707 | DSS_mutex_unlock(&dsslock); 708 | return 1; 709 | }; 710 | 711 | 712 | /*** 713 | Gets the next item from the darksidesync queue. 714 | If you use the UDP notifications, you MUST also read from the UDP socket to 715 | clear the received packet from the socket buffer. 716 | 717 | NOTE: some of the return values will be generated by 718 | the client library (that is using darksidesync to get its data delivered to the Lua state) and other 719 | return values will be inserted by darksidesync. 720 | @function poll 721 | @return (by DSS) queuesize of remaining items (or -1 if there was nothing on the queue to begin with) 722 | @return (by client) Lua callback function to handle the data 723 | @return Table with arguments for the Lua callback, this contains (by client library) any other parameters as delivered by the async callback. Optionally, if the async thread requires a result to be returned, a `waitingthread_callback` function (by DSS) is inserted at position 1 (but only if the async callback expects Lua to deliver a result, in this case the async callback thread will be blocked until the `waitingthread_callback` is called) 724 | @usage 725 | local runcallbacks() 726 | local count, callback, args = darksidesync.poll() 727 | if count == -1 then return end -- queue was empty, nothing to do 728 | callback(unpack(args)) -- execute callback 729 | if count > 0 then 730 | print("there is more to do; " .. tostring(count) .. " items are still in the queue.") 731 | else 732 | print("We're done for now.") 733 | end 734 | end 735 | */ 736 | 737 | // Lua function to get the next item from the queue, its decode 738 | // function will be called to do what needs to be done 739 | static int L_poll(lua_State *L) 740 | { 741 | pglobalRecord g = DSS_getvalidglobals(L); // won't return on error 742 | int result = 0; 743 | 744 | lua_settop(L, 0); // clear stack 745 | 746 | DSS_mutex_lock(&dsslock); 747 | if (g->QueueCount > 0) 748 | { 749 | // Go decode oldest item 750 | result = delivery_decode(g->QueueStart, L); 751 | } 752 | else 753 | { 754 | // Nothing in queue 755 | lua_pushinteger(L, -1); // return -1 to indicate queue was empty when called 756 | result = 1; 757 | } 758 | 759 | DSS_mutex_unlock(&dsslock); 760 | return result; 761 | }; 762 | 763 | 764 | /*** 765 | Returns the current size of the darksidesync queue. 766 | @function queuesize 767 | @return number of items in the queue 768 | */ 769 | static int L_queuesize(lua_State *L) 770 | { 771 | pglobalRecord g = DSS_getvalidglobals(L); // won't return on error 772 | lua_settop(L, 0); // clear stack 773 | DSS_mutex_lock(&dsslock); 774 | lua_pushinteger(L, g->QueueCount); 775 | DSS_mutex_unlock(&dsslock); 776 | return 1; 777 | }; 778 | 779 | // Execute the return callback, either regular or from garbage collector 780 | static int L_return_internal(lua_State *L, BOOL garbage) 781 | { 782 | int result = 0; 783 | pQueueItem pqi = NULL; 784 | pQueueItem* rqi = (pQueueItem*)luaL_checkudata(L, 1, DSS_QUEUEITEM_MT); // first item must be our queue item 785 | 786 | DSS_mutex_lock(&dsslock); 787 | pqi = *rqi; 788 | if (pqi != NULL) result = delivery_return(pqi, L, garbage); 789 | DSS_mutex_unlock(&dsslock); 790 | return result; 791 | } 792 | 793 | // GC method for queue items waiting for a 'return' callback 794 | static int L_queueItemGC(lua_State *L) 795 | { 796 | return L_return_internal(L, TRUE); 797 | } 798 | 799 | /*** 800 | Callback function to set the results of an async callback. The 'waiting-thread' callback is collected from 801 | the `poll` method in case a background thread is blocked and waiting for a result. 802 | Call this function with the results to return to the async callback. 803 | @function waitingthread_callback 804 | @param ... parameters to be delivered to the async callback. This depends on what the client library expects 805 | @return depends on client library implementation 806 | @see poll 807 | */ 808 | // Return method for queue items waiting for a 'return' callback 809 | static int L_return(lua_State *L) 810 | { 811 | return L_return_internal(L, FALSE); 812 | } 813 | 814 | /* 815 | ** =============================================================== 816 | ** Library initialization 817 | ** =============================================================== 818 | */ 819 | static const struct luaL_Reg DarkSideSync[] = { 820 | {"poll",L_poll}, 821 | {"getport",L_getport}, 822 | {"setport",L_setport}, 823 | {"queuesize",L_queuesize}, 824 | {NULL,NULL} 825 | }; 826 | 827 | DSS_API int luaopen_darksidesync(lua_State *L) 828 | { 829 | pglobalRecord g; 830 | int errcode; 831 | 832 | #ifdef _DEBUG 833 | OutputDebugStringA("DSS: LuaOpen started...\n"); 834 | #endif 835 | 836 | if (DSS_initialized == NULL) // first initialization of first mutex, unsafe? 837 | { 838 | // Initialize all statics 839 | if (DSS_mutex_init(&dsslock) != 0) 840 | { 841 | // an error occured while initializing the 2 global mutexes 842 | return luaL_error(L,"DSS had an error initializing its mutexes (utillock)"); 843 | } 844 | DSS_initialized = &luaopen_darksidesync; //point to 'something', no longer NULL 845 | 846 | // Initializes API structure for API 1.0 (static, so only once) 847 | DSS_api_1v0.version = DSS_API_1v0_KEY; 848 | DSS_api_1v0.reg = (DSS_register_1v0_t)&DSS_register_1v0; 849 | DSS_api_1v0.getutilid = (DSS_getutilid_1v0_t)&DSS_getutilid_1v0; 850 | DSS_api_1v0.deliver = (DSS_deliver_1v0_t)&DSS_deliver_1v0; 851 | DSS_api_1v0.unreg = (DSS_unregister_1v0_t)&DSS_unregister_1v0; 852 | } 853 | 854 | // Create metatable for userdata's waiting for 'return' callback 855 | luaL_newmetatable(L, DSS_QUEUEITEM_MT); 856 | lua_pushstring(L, "__index"); 857 | lua_pushvalue(L, -2); 858 | lua_settable(L, -3); // copy metatable itself 859 | lua_pushstring(L, "__gc"); 860 | lua_pushcfunction(L, &L_queueItemGC); 861 | lua_settable(L, -3); 862 | lua_pushstring(L, "__call"); 863 | lua_pushcfunction(L, &L_return); 864 | lua_settable(L, -3); 865 | 866 | // Create a metatable to GC the global data upon exit 867 | luaL_newmetatable(L, DSS_GLOBALS_MT); 868 | lua_pushstring(L, "__gc"); 869 | lua_pushcfunction(L, &DSS_clearstateglobals); 870 | lua_settable(L, -3); 871 | lua_pop(L,1); 872 | 873 | // get or create the stateglobals 874 | g = DSS_getstateglobals(L, &errcode); 875 | if (errcode == DSS_ERR_NO_GLOBALS) 876 | { 877 | // this is expected ! there shouldn't be a record yet on startup 878 | g = DSS_newstateglobals(L, &errcode); // create a new one 879 | if (errcode != DSS_SUCCESS) 880 | { 881 | if (errcode == DSS_ERR_OUT_OF_MEMORY) 882 | return luaL_error(L, "Out of memory: DSS failed to create a global data structure for the LuaState"); 883 | return luaL_error(L, "Unknown error occured while trying to create a global data structure for the LuaState"); 884 | } 885 | } 886 | g->DSS_status = DSS_STATUS_STARTED; 887 | 888 | // Store pointer to my api structure in the Lua registry 889 | // for backgroundworkers to collect there 890 | lua_settop(L,0); // clear stack 891 | lua_newtable(L); // push a new table for DSS 892 | // add a DSS version key to the DSS table 893 | lua_pushstring(L, DSS_VERSION); 894 | lua_setfield(L, 1, DSS_VERSION_KEY); 895 | // add the DSS api version 1.0 to the DSS table 896 | lua_pushlightuserdata(L,&DSS_api_1v0); 897 | lua_setfield(L, 1, DSS_API_1v0_KEY); 898 | // Push overall DSS table onto the Lua registry 899 | lua_setfield(L, LUA_REGISTRYINDEX, DSS_REGISTRY_NAME); 900 | 901 | // Increase state count and start network if first 902 | DSS_mutex_lock(&dsslock); 903 | if (statecount == 0) 904 | { 905 | udpsocket_networkInit(); 906 | } 907 | statecount = statecount + 1; 908 | DSS_mutex_unlock(&dsslock); 909 | 910 | luaL_register(L,"darksidesync",DarkSideSync); 911 | #ifdef _DEBUG 912 | OutputDebugStringA("DSS: LuaOpen completed\n"); 913 | #endif 914 | return 1; 915 | }; 916 | 917 | -------------------------------------------------------------------------------- /darksidesync/darksidesync.geany: -------------------------------------------------------------------------------- 1 | 2 | [indentation] 3 | indent_width=4 4 | indent_type=1 5 | indent_hard_tab_width=8 6 | detect_indent=false 7 | indent_mode=2 8 | 9 | [project] 10 | name=DarkSideSync 11 | base_path=/home/thijs/CodeLite/DarkSideSync/ 12 | description= 13 | 14 | [long line marker] 15 | long_line_behaviour=1 16 | long_line_column=72 17 | 18 | [files] 19 | current_page=9 20 | FILE_NAME_0=0;C;0;16;1;1;0;/home/thijs/CodeLite/DarkSideSync/src/darksidesync.c;0;4 21 | FILE_NAME_1=0;C;0;16;1;1;0;/home/thijs/CodeLite/DarkSideSync/src/darksidesync.h;0;4 22 | FILE_NAME_2=0;C;0;16;1;1;0;/home/thijs/CodeLite/DarkSideSync/src/locking.c;0;4 23 | FILE_NAME_3=0;C;0;16;1;1;0;/home/thijs/CodeLite/DarkSideSync/src/locking.h;0;4 24 | FILE_NAME_4=0;C;0;16;1;1;0;/home/thijs/CodeLite/DarkSideSync/src/udpsocket.c;0;4 25 | FILE_NAME_5=0;C;0;16;1;1;0;/home/thijs/CodeLite/DarkSideSync/src/udpsocket.h;0;4 26 | FILE_NAME_6=3374;Make;0;16;1;1;0;/home/thijs/CodeLite/DarkSideSync/src/makefile;0;4 27 | FILE_NAME_7=2048;C;0;16;1;1;0;/usr/include/lua5.1/lua.h;0;4 28 | FILE_NAME_8=190;C;0;16;1;1;0;/usr/include/lua5.1/lauxlib.h;0;4 29 | FILE_NAME_9=2837;Lua;0;16;1;1;0;/home/thijs/CodeLite/DarkSideSync/src/dss.lua;0;4 30 | 31 | [build-menu] 32 | CFT_00_LB=_Compile 33 | CFT_00_CM=gcc -Wall -I/usr/include/lua5.1 -c "%f" 34 | CFT_00_WD= 35 | CFT_01_LB=_Build 36 | CFT_01_CM=gcc -Wall -I/usr/include/lua5.1 -o "%e" "%f" 37 | CFT_01_WD= 38 | filetypes=C; 39 | NF_00_LB=_Make 40 | NF_00_CM=make linux 41 | NF_00_WD= 42 | NF_01_LB=Make Custom _Target 43 | NF_01_CM=make 44 | NF_01_WD= 45 | -------------------------------------------------------------------------------- /darksidesync/darksidesync.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef darksidesync_h 3 | #define darksidesync_h 4 | 5 | #include 6 | #include "darksidesync_api.h" 7 | #include "udpsocket.h" 8 | #include "locking.h" 9 | #include "waithandle.h" 10 | 11 | ////////////////////////////////////////////////////////////// 12 | // symbol list // 13 | ////////////////////////////////////////////////////////////// 14 | 15 | // Define symbol for last queue item, independent of utilid 16 | #define DSS_LASTITEM NULL 17 | 18 | // Symbols for library status 19 | #define DSS_STATUS_STARTED -1 20 | #define DSS_STATUS_STOPPING -2 21 | #define DSS_STATUS_STOPPED -3 22 | 23 | // Lua registry key for globaldata structure 24 | #define DSS_GLOBALS_KEY "DSS.globals" 25 | // Lua registry key for metatable of the global structure userdata 26 | #define DSS_GLOBALS_MT "DSS.globals.mt" 27 | // Lua registry key for metatable of queueItems waiting for 'return' callback 28 | #define DSS_QUEUEITEM_MT "DSS.queueitem.mt" 29 | 30 | // Define platform specific extern statement 31 | #ifdef WIN32 32 | #define DSS_API __declspec(dllexport) 33 | #else 34 | #define DSS_API extern 35 | #endif 36 | 37 | // Windows BOOL compatibility for other platforms 38 | #ifndef BOOL 39 | #define BOOL int 40 | #define TRUE 1 41 | #define FALSE 0 42 | #endif 43 | 44 | ////////////////////////////////////////////////////////////// 45 | // C side structures for registration and globals // 46 | ////////////////////////////////////////////////////////////// 47 | 48 | // first forward declare these to resolve circular references 49 | typedef struct utilReg *putilRecord; 50 | typedef struct qItem *pQueueItem; 51 | typedef struct stateGlobals *pglobalRecord; 52 | 53 | // structure for registering utilities 54 | typedef struct utilReg { 55 | DSS_cancel_1v0_t pCancel; // pointer to cancel function 56 | putilRecord pNext; // Next item in list 57 | putilRecord pPrevious; // Previous item in list 58 | pglobalRecord pGlobals; // pointer to the global data for this utility 59 | void* libid; // unique library specific ID 60 | } utilRecord; 61 | 62 | // Structure for storing data from an async callback in the queue 63 | // NOTE: while waiting for 'poll' to be called it will be in the queue, 64 | // while waiting for 'return' callback, it will be in a userdata 65 | typedef struct qItem { 66 | putilRecord utilid; // unique ID to utility 67 | pDSS_waithandle pWaitHandle; // Wait handle to block thread while wait for return to be called 68 | void* pData; // Data to be decoded 69 | pQueueItem pNext; // Next item in queue/list 70 | pQueueItem pPrevious; // Previous item in queue/list 71 | pQueueItem* udata; // a userdata containing a pointer to this qItem 72 | // API functions at the end, so casting of future versions can be done 73 | DSS_decoder_1v0_t pDecode; // Pointer to the decode function, if NULL then it was already called 74 | DSS_return_1v0_t pReturn; // Pointer to the return function 75 | } QueueItem; 76 | 77 | // structure for state global variables to be stored outside of the LuaState 78 | // this is required to be able to access them from an async callback 79 | // (which cannot call into lua to collect global data there) 80 | typedef struct stateGlobals { 81 | //DSS_mutex_t lock; // lock to protect struct data 82 | int volatile udpport; // 0 = no notification 83 | udpsocket_t socket; // structure with socket data 84 | int volatile DSS_status; // Status of library 85 | // Elements for the async data queue 86 | pQueueItem volatile QueueStart; // Holds first element in the queue 87 | pQueueItem volatile QueueEnd; // Holds the last item in the queue 88 | int volatile QueueCount; // Count of items in queue 89 | // Elements for the userdata list 90 | pQueueItem volatile UserdataStart; // Holds first element in the list 91 | } globalRecord; 92 | 93 | 94 | #endif /* darksidesync_h */ 95 | -------------------------------------------------------------------------------- /darksidesync/darksidesync.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | 14 | {FEDD20FF-D6EE-4B86-AB01-635CC9CDB6D7} 15 | luaexit 16 | Win32Proj 17 | 18 | 19 | 20 | DynamicLibrary 21 | Unicode 22 | true 23 | 24 | 25 | DynamicLibrary 26 | Unicode 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | <_ProjectFileVersion>10.0.40219.1 40 | $(SolutionDir)$(Configuration)\ 41 | $(Configuration)\ 42 | true 43 | $(SolutionDir)$(Configuration)\ 44 | $(Configuration)\ 45 | false 46 | AllRules.ruleset 47 | 48 | 49 | AllRules.ruleset 50 | 51 | 52 | 53 | 54 | 55 | Disabled 56 | C:\Users\Public\Lua\5.1\include;%(AdditionalIncludeDirectories) 57 | WIN32;_DEBUG;_WINDOWS;_USRDLL;LUA_TEMPLATE_EXPORTS;%(PreprocessorDefinitions) 58 | true 59 | EnableFastChecks 60 | MultiThreadedDebugDLL 61 | 62 | 63 | Level3 64 | EditAndContinue 65 | CompileAsC 66 | 67 | 68 | lua51.lib;wsock32.lib;%(AdditionalDependencies) 69 | C:\Users\Public\Lua\5.1\lib;%(AdditionalLibraryDirectories) 70 | true 71 | Windows 72 | MachineX86 73 | 74 | 75 | "$(ProjectDir)install.bat" 76 | 77 | 78 | 79 | 80 | MaxSpeed 81 | true 82 | WIN32;NDEBUG;_WINDOWS;_USRDLL;LUAEXIT_EXPORTS;%(PreprocessorDefinitions) 83 | MultiThreadedDLL 84 | true 85 | 86 | 87 | Level3 88 | ProgramDatabase 89 | C:\Users\Public\Lua\5.1lfw\include;%(AdditionalIncludeDirectories) 90 | 91 | 92 | true 93 | Windows 94 | true 95 | true 96 | MachineX86 97 | C:\Users\Public\Lua\5.1lfw\lib;%(AdditionalLibraryDirectories) 98 | lua51.lib;wsock32.lib;%(AdditionalDependencies) 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /darksidesync/darksidesync.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | Source Files 23 | 24 | 25 | Source Files 26 | 27 | 28 | Source Files 29 | 30 | 31 | Source Files 32 | 33 | 34 | Source Files 35 | 36 | 37 | 38 | 39 | Source Files 40 | 41 | 42 | Source Files 43 | 44 | 45 | Source Files 46 | 47 | 48 | Resource Files 49 | 50 | 51 | Resource Files 52 | 53 | 54 | Resource Files 55 | 56 | 57 | 58 | 59 | Header Files 60 | 61 | 62 | Header Files 63 | 64 | 65 | Header Files 66 | 67 | 68 | Header Files 69 | 70 | 71 | Header Files 72 | 73 | 74 | Header Files 75 | 76 | 77 | -------------------------------------------------------------------------------- /darksidesync/darksidesync_api.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef darksidesync_api_h 3 | #define darksidesync_api_h 4 | 5 | #include 6 | 7 | // Setup version information 8 | #define DSS_VERSION "0.1" 9 | // Define global names for the Lua registry 10 | #define DSS_REGISTRY_NAME "DSS.DarkSideSync" // key to registry to where DSS will store its API's 11 | #define DSS_VERSION_KEY "Version" // key to version info within DSS table 12 | #define DSS_API_1v0_KEY "DSS API 1v0" // key to struct with this API version (within DSS table), also used as version string in API struct 13 | 14 | ////////////////////////////////////////////////////////////// 15 | // IMPORTANT USAGE NOTES !!!! // 16 | ////////////////////////////////////////////////////////////// 17 | // If you intend to create a library/binding and use DarkSideSync 18 | // for synchronizing, then DO NOT LINK against DarkSideSync! 19 | // Copy the 'darksidesync_api.h' file into your project and 20 | // include it. It only contains definitions. That is all you 21 | // need. 22 | // If you want the 'ready-to-go' approach, then copy also the 23 | // 'darksidesync_aux.c' and 'darksidesync_aux.h' files and 24 | // include only 'darksidesync_aux.h'. 25 | // The '_aux' files should not be included when building 26 | // DarkSideSync itself. 27 | 28 | ////////////////////////////////////////////////////////////// 29 | // C side prototypes, implemented by background worker // 30 | ////////////////////////////////////////////////////////////// 31 | 32 | // The backgroundworker must provide this function. The function 33 | // will get a pointer to previously delivered pData and is responsible 34 | // for decoding this pData and take appropriate action. 35 | // @arg1; the Lua state (or NULL if items are being cancelled) 36 | // @arg2; the pData previously delivered. 37 | // @arg3; the unique utility ID for which the call is being made (in case 38 | // the utility has been 'required' in multiple parallel lua states) 39 | // If @arg1 is NULL, then resources should be released to cleanup, upon 40 | // returning the waiting thread will be released. 41 | // NOTE: Must always return; do not use code that causes longjumps etc. 42 | // like luaL_error etc. 43 | // Should return; 44 | // >0 ;nr of items on stack, 1st item must be lua function, to be called with remaining items as args 45 | // upon returning a new 1st argument will be inserted; a callback as reference to the waiting thread 46 | // (`waitingthread_callback`; only if a 'return' callback was specified on calling 'deliver' obviously) 47 | // 0 ;cycle complete, do not create userdata and release the waiting thread (if set to wait) 48 | // if 0 the DSS process for this callback stops here, so any resources should be released here 49 | // before returning, or by the blocked thread after it is released. 50 | // NOTE: the Lua code should at some point call `waitingthread_callback` on the userdata. This will 51 | // trigger the 'return' function below. 52 | typedef int (*DSS_decoder_1v0_t) (lua_State *L, void* pData, void* utilid); 53 | 54 | // The backgroundworker must provide this function. The function 55 | // will get a pointer to previously delivered pData and is responsible 56 | // for retrieving Lua results and store them in pData, so the initial calling 57 | // (and currently blocked) thread can handle the results when released. 58 | // This function is called when 'waitingthread_callback' is called on the userdata returned 59 | // from the 'deliver' function. 60 | // When this function returns the blocked thread will be released. 61 | // @arg1; the Lua state (or NULL if items are being cancelled) 62 | // @arg2; the pData previously delivered. 63 | // @arg3; the unique utility ID for which the call is being made (in case 64 | // the utility has been 'required' in multiple parallel lua states) 65 | // @arg4; BOOL indicating (TRUE) whether the function was called from the 66 | // __GC method of the userdata. 67 | // @arg-Lua; on the Lua stack will be the parameters provided when calling the 68 | // `waitingthread_callback` function, the callback/userdata itself (1st arg) 69 | // will have been removed from the stack. 70 | // returns: number of lua args on stack 71 | // NOTE: 1) This is the final call, so any resources must be released here, or 72 | // by the unblocked thread 73 | // 2) Must always return; do not use code that causes longjumps etc. like 74 | // luaL_error etc. 75 | typedef int (*DSS_return_1v0_t) (lua_State *L, void* pData, void* utilid, int garbage); 76 | 77 | // The backgroundworker must provide this function. A pointer 78 | // to this method should be provided when calling the DSS_register function. 79 | // When called, the backgroundworker should stop delivering and 80 | // unregister itself with DSS. 81 | // @arg1; the unique utility ID for which the call is being made (in case 82 | // the utility has been 'required' in multiple parallel lua states) 83 | typedef void (*DSS_cancel_1v0_t) (void* utilid); 84 | 85 | 86 | ////////////////////////////////////////////////////////////// 87 | // C side prototypes, implemented by DSS // 88 | ////////////////////////////////////////////////////////////// 89 | 90 | // The backgroundworker can call this function (see 91 | // DSS_REGISTRY_NAME for collecting it) to deliver data 92 | // to the Lua state. 93 | // @arg1; ID of utility delivering (see register() function) 94 | // @arg2; pointer to a decoder function (see DSS_decoder_t above) 95 | // @arg3; pointer to a return function (see DSS_decoder_t above) 96 | // @arg4; pointer to some piece of data. 97 | // @returns; DSS_SUCCESS, DSS_ERR_INVALID_UTILID, DSS_ERR_UDP_SEND_FAILED, 98 | // DSS_ERR_OUT_OF_MEMORY, DSS_ERR_NOT_STARTED, DSS_ERR_NO_DECODE_PROVIDED 99 | // NOTE1: DSS_ERR_UDP_SEND_FAILED means that the data was still delivered to the 100 | // queue, only the notification failed, for the other errors, it will not be 101 | // queued (see return codes for warnings vs errors). 102 | // NOTE2: edgecase due to synchronization, when delivering while DSS is stopping 103 | // DSS_ERR_INVALID_UTILID may be returned, even if cancel() was not called 104 | // yet, so this should always be checked 105 | // NOTE3: 'return' callback, if provided, the thread will be blocked until the 106 | // DSS process for this callback is complete. If NULL, the thread returns 107 | // immediately. 108 | typedef int (*DSS_deliver_1v0_t) (void* utilid, DSS_decoder_1v0_t pDecode, DSS_return_1v0_t pReturn, void* pData); 109 | 110 | // returns the utilid, for the combination of the LuaState and libid provided 111 | // when handling a call form Lua, this enables access to the utilid, without 112 | // having to explicitly manage utilid's across different LuaStates. 113 | // @arg1; LuaState pointer, this identifies a unique LuaState 114 | // @arg2; libid, generic pointer as an ID to a library (ID is shared across LuaStates) 115 | // @arg3; int pointer that will receive the error code DSS_ERR_UNKNOWN_LIB, 116 | // DSS_ERR_NOT_STARTED or DSS_SUCCESS if no error (param may be NULL) 117 | // Returns: utilid, the ID that uniqueliy identifies the combination of a 118 | // LuaState and a background worker, or NULL upon failure (check errcode). 119 | typedef void* (*DSS_getutilid_1v0_t) (lua_State *L, void* libid, int* errcode); 120 | 121 | // The background worker should call this to register and get its ID 122 | // @arg1; pointer to LuaState 123 | // @arg2; ID for the library registering, can simply be; 124 | // static void* myLibID = &myLibID; // pointer to itself 125 | // @arg3; pointer to the background workers cancel() method 126 | // @arg4; int pointer that will receive the error code, or DSS_SUCCESS if no error (param may be NULL) 127 | // @returns; unique ID (for the utility to use in other calls), or NULL and error 128 | // DSS_ERR_NOT_STARTED, DSS_ERR_NO_CANCEL_PROVIDED, DSS_ERR_OUT_OF_MEMORY, DSS_ERR_ALREADY_REGISTERED 129 | // NOTE: if the utility was already registered, it will return the existing ID, 130 | // but it will ignore all provided parameters (nothing will be changed) 131 | typedef void* (*DSS_register_1v0_t) (lua_State *L, void* libid, DSS_cancel_1v0_t pCancel, int* errcode); 132 | 133 | // The background worker should call this to unregister itself on 134 | // shutdown. Any items left in the queue will be cancelled. 135 | // @arg1; the ID of the background worker to unregister 136 | // @returns: DSS_SUCCESS, DSS_ERR_INVALID_UTILID 137 | typedef int (*DSS_unregister_1v0_t) (void* utilid); 138 | 139 | // Define structure to contain the API for version 1.0 140 | typedef struct DSS_api_1v0_s *pDSS_api_1v0_t; 141 | typedef struct DSS_api_1v0_s { 142 | const char* version; 143 | DSS_register_1v0_t reg; 144 | DSS_getutilid_1v0_t getutilid; 145 | DSS_deliver_1v0_t deliver; 146 | DSS_unregister_1v0_t unreg; 147 | } DSS_api_1v0_t; 148 | 149 | 150 | ////////////////////////////////////////////////////////////// 151 | // C side DSS return codes // 152 | ////////////////////////////////////////////////////////////// 153 | // Warnings are informative, errors should be dealt with 154 | // Example; DSS_ERR_UDP_SEND_FAILED usually means that a notification 155 | // was not send, but the element was handled properly. 156 | #define DSS_SUCCESS -100 // success 157 | // Warnings > DSS_SUCCESS 158 | #define DSS_ERR_UDP_SEND_FAILED -99 // notification failed due to UDP/socket error 159 | // Errors < DSS_SUCCESS 160 | #define DSS_ERR_INVALID_UTILID -101 // provided ID does not exist/invalid 161 | #define DSS_ERR_NOT_STARTED -102 // DSS hasn't been started, or was already stopping/stopped 162 | #define DSS_ERR_NO_CANCEL_PROVIDED -103 // When registering the cancel method is required 163 | #define DSS_ERR_OUT_OF_MEMORY -104 // memory allocation failed 164 | #define DSS_ERR_NO_DECODE_PROVIDED -105 // no decode function provided when delivering 165 | #define DSS_ERR_NO_GLOBALS -106 // LuaState does not have a global record 166 | #define DSS_ERR_UNKNOWN_LIB -107 // The library requesting its utildid is unregistered 167 | #define DSS_ERR_ALREADY_REGISTERED -108 // trying to register the same lib, in the same lua state again 168 | #endif /* darksidesync_api_h */ 169 | -------------------------------------------------------------------------------- /darksidesync/darksidesync_aux.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "darksidesync_api.h" 5 | #include "darksidesync_aux.h" 6 | 7 | // will get the api struct (pointer) for the api version 1v0 8 | // in case of errors it will provide a proper error message and call 9 | // luaL_error. In case of an error the call will not return. 10 | void DSS_initialize(lua_State *L, DSS_cancel_1v0_t pCancel) 11 | { 12 | pDSS_api_1v0_t api_copy = NULL; 13 | int errcode; 14 | 15 | // Collect the table with the global DSS data from the register 16 | lua_getfield(L, LUA_REGISTRYINDEX, DSS_REGISTRY_NAME); 17 | if (lua_istable(L, -1) == 0) 18 | { 19 | // following call does not return 20 | luaL_error(L, "No DSS registry table found, make sure to require 'darksidesync' first."); 21 | } 22 | // Now get the specified API structure 23 | lua_getfield(L, -1, DSS_API_1v0_KEY); 24 | if (lua_islightuserdata (L, -1) == 0) 25 | { 26 | // the API struct wasn't found 27 | char str[150]; // appr 40 chars for version string, should suffice 28 | lua_getfield(L, -2, DSS_VERSION_KEY); 29 | if (lua_isstring(L, -1)) 30 | { 31 | sprintf(str, "Loaded DSS version '%s' does not support API version '%s'. Make sure to use the proper DSS version.", lua_tostring(L, -1), DSS_API_1v0_KEY); 32 | } 33 | else 34 | { 35 | sprintf(str, "Loaded DSS version is unknown, it does not support API version '%s'. Make sure to use the proper DSS version.", DSS_API_1v0_KEY); 36 | } 37 | // following call does not return 38 | luaL_error(L, str); 39 | } 40 | api_copy = (pDSS_api_1v0_t)lua_touserdata(L, -1); 41 | lua_pop(L,2); // pop apistruct and DSS global table 42 | 43 | // Now register ourselves 44 | api_copy->reg(L, DSS_LibID, pCancel, &errcode); 45 | if (errcode != DSS_SUCCESS) 46 | { 47 | // The error calls below will not return 48 | switch (errcode) { 49 | case DSS_ERR_NOT_STARTED: luaL_error(L, "DSS was not started, or already stopped again."); 50 | case DSS_ERR_NO_CANCEL_PROVIDED: luaL_error(L, "No proper cancel method was provided when initializing DSS."); 51 | case DSS_ERR_OUT_OF_MEMORY: luaL_error(L, "Memory allocation error while initializing DSS"); 52 | case DSS_ERR_ALREADY_REGISTERED: luaL_error(L, "Library already registered with DSS for this LuaState"); 53 | default: luaL_error(L, "An unknown error occured while initializing DSS."); 54 | } 55 | } 56 | // Release our api; assumed atomical 57 | DSSapi = api_copy; 58 | return; 59 | } 60 | 61 | // Collect the utilid, or throw Lua error if none 62 | void* DSS_getutilid(lua_State *L) 63 | { 64 | int err = DSS_SUCCESS; 65 | void* result = NULL; 66 | if (DSSapi != NULL) 67 | { 68 | result = DSSapi->getutilid(L, DSS_LibID, &err); 69 | if (err != DSS_SUCCESS) 70 | { 71 | luaL_error(L, "Cannot collect utilid from DSS"); // call won't return 72 | } 73 | } 74 | else 75 | { 76 | luaL_error(L, "DSS not started"); // call won't return 77 | } 78 | return result; 79 | } 80 | 81 | // Deliver data to the Lua state asynchroneously 82 | // checks existence of the API 83 | int DSS_deliver(void* utilid, DSS_decoder_1v0_t pDecode, DSS_return_1v0_t pReturn, void* pData) 84 | { 85 | if (DSSapi != NULL) 86 | { 87 | return DSSapi->deliver(utilid, pDecode, pReturn, pData); 88 | } 89 | else 90 | { 91 | return DSS_ERR_NOT_STARTED; 92 | } 93 | } 94 | 95 | // at shutdown, this will safely unregister the library 96 | // use lua_State param if called from userdata __gc method 97 | // use utilid param if called from the DSS cancel() method 98 | // one param must be provided, lua_State has highest precedence 99 | void DSS_shutdown(lua_State *L, void* utilid) 100 | { 101 | pDSS_api_1v0_t api_copy = DSSapi; 102 | if (DSSapi != NULL) 103 | { 104 | if ((L == NULL) && ( utilid == NULL)) 105 | { 106 | // TODO: must fail hard here 107 | } 108 | // First remove ref. assumed to be atomical 109 | DSSapi = NULL; 110 | // If we got a Lua state, go lookup our utilid 111 | if (L != NULL) utilid = api_copy->getutilid(L, DSS_LibID, NULL); 112 | // Unregister 113 | if (utilid != NULL) api_copy->unreg(utilid); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /darksidesync/darksidesync_aux.h: -------------------------------------------------------------------------------- 1 | #ifndef darksidesync_aux_h 2 | #define darksidesync_aux_h 3 | 4 | #include "darksidesync_api.h" 5 | 6 | 7 | // static pointer to itself, uniquely identifies this library 8 | static void* DSS_LibID = &DSS_LibID; 9 | // Static pointer to the DSS api, will be set by the initialize function below 10 | static pDSS_api_1v0_t DSSapi = NULL; // Static no issue, because version 1v0 is same in all libraries 11 | 12 | 13 | 14 | void DSS_initialize(lua_State *L, DSS_cancel_1v0_t pCancel); 15 | void* DSS_getutilid(lua_State *L); 16 | int DSS_deliver(void* utilid, DSS_decoder_1v0_t pDecode, DSS_return_1v0_t pReturn, void* pData); 17 | void DSS_shutdown(lua_State *L, void* utilid); 18 | 19 | #endif /* darksidesync_aux_h */ -------------------------------------------------------------------------------- /darksidesync/debug.lua: -------------------------------------------------------------------------------- 1 | local dss = require("darksidesync") 2 | 3 | for k,v in pairs(dss) do 4 | print (k, type(v)) 5 | end 6 | print ("\nDSS loaded, Press enter...") 7 | io.read() 8 | 9 | local luaexit = require("luaexit") 10 | for k,v in pairs(luaexit) do 11 | print (k, type(v)) 12 | end 13 | 14 | print ("\nLuaExit loaded, Press enter...") 15 | io.read() 16 | -------------------------------------------------------------------------------- /darksidesync/delivery.c: -------------------------------------------------------------------------------- 1 | #include "delivery.h" 2 | 3 | // New constructor 4 | // Creates a element for delivery and places it in the queue, waiting for a 5 | // poll to arrive. Creates the waithandle if required and sends the UDP 6 | // notification if set. 7 | // 8 | // @returns; NULL if it failed 9 | // @err; DSS_SUCCESS, DSS_ERR_UDP_SEND_FAILED, DSS_ERR_INVALID_UTILID, 10 | // DSS_ERR_OUT_OF_MEMORY, DSS_ERR_NOT_STARTED 11 | // 12 | // Notes: 13 | // * if it returns an element, the result may still be a warning (see return codes; errors vs warnings) 14 | // * Utilid MUST be valid before calling 15 | 16 | pQueueItem delivery_new(putilRecord utilid, DSS_decoder_1v0_t pDecode, DSS_return_1v0_t pReturn, void* pData, int* err) 17 | { 18 | pglobalRecord g; 19 | int result; 20 | char buff[20]; 21 | pDSS_waithandle wh = NULL; 22 | pQueueItem pqi = NULL; 23 | 24 | // if no err provided, set it now. 25 | if (err == NULL) err = &result; 26 | *err = DSS_SUCCESS; 27 | 28 | g = utilid->pGlobals; 29 | if (g->DSS_status != DSS_STATUS_STARTED) 30 | { 31 | // lib not started yet (or stopped already), exit 32 | *err = DSS_ERR_NOT_STARTED; 33 | return NULL; 34 | } 35 | 36 | if (NULL == (pqi = (pQueueItem)malloc(sizeof(QueueItem)))) 37 | { 38 | *err = DSS_ERR_OUT_OF_MEMORY; 39 | return NULL; // exit, memory alloc failed 40 | } 41 | 42 | if (pReturn != NULL) // only create waithandle if a return function specified 43 | { 44 | wh = DSS_waithandle_create(); 45 | if (wh == NULL) 46 | { 47 | // error, resource alloc failed 48 | free(pqi); 49 | *err = DSS_ERR_OUT_OF_MEMORY; 50 | return NULL; 51 | } 52 | } 53 | 54 | pqi->pWaitHandle = wh; 55 | pqi->utilid = utilid; 56 | pqi->pDecode = pDecode; 57 | pqi->pReturn = pReturn; 58 | pqi->pData = pData; 59 | pqi->pNext = NULL; 60 | pqi->pPrevious = NULL; 61 | pqi->udata = NULL; 62 | 63 | if (g->QueueStart == NULL) 64 | { 65 | // first item in queue 66 | g->QueueStart = pqi; 67 | g->QueueEnd = pqi; 68 | } 69 | else 70 | { 71 | // append to queue 72 | g->QueueEnd->pNext = pqi; 73 | pqi->pPrevious = g->QueueEnd; 74 | g->QueueEnd = pqi; 75 | } 76 | 77 | g->QueueCount += 1; 78 | 79 | sprintf(buff, " %d", g->QueueCount); // convert to string 80 | 81 | // Now send notification packet 82 | if (g->udpport != 0) 83 | { 84 | if (udpsocket_send(g->socket, buff) == 0) 85 | { 86 | // sending failed, retry; close create new and do again 87 | udpsocket_close(g->socket); 88 | g->socket = udpsocket_new(g->udpport); 89 | if (udpsocket_send(g->socket, buff) == 0) 90 | { 91 | *err = DSS_ERR_UDP_SEND_FAILED; // store failure to report 92 | } 93 | } 94 | } 95 | 96 | return pqi; 97 | }; 98 | 99 | 100 | // Decoder 101 | // removes an item from the queue and deals with the POLL step. 102 | // the decode callback will be called to do what needs to be done 103 | // returns (on Lua stack): 104 | // 1st: queuesize of remaining items or; 105 | // -1 to indicate there was nothing in the queue to begin with 106 | // 2nd: lua callback function to handle the data 107 | // 3rd: table containing all callback arguments with; 108 | // pos 1 : userdata waiting for the response (only if a 'return' call is still valid) 109 | // pos 2+: any stuff left by decoder after the callback function (2nd above) 110 | // 111 | // Note: if lua_state == NULL then the item will be cancelled 112 | int delivery_decode(pQueueItem pqi, lua_State *L) 113 | { 114 | int result = 0; 115 | pQueueItem* udata = NULL; 116 | pglobalRecord g = pqi->utilid->pGlobals; 117 | 118 | // Remove item from queue 119 | if (pqi == g->QueueStart) g->QueueStart = pqi->pNext; 120 | if (pqi == g->QueueEnd) g->QueueEnd = pqi->pPrevious; 121 | if (pqi->pPrevious != NULL) pqi->pPrevious->pNext = pqi->pNext; 122 | if (pqi->pNext != NULL) pqi->pNext->pPrevious = pqi->pPrevious; 123 | // cleanup results 124 | pqi->pNext = NULL; 125 | pqi->pPrevious = NULL; 126 | g->QueueCount -= 1; 127 | 128 | // execute callback, set to NULL to indicate call is done 129 | result = pqi->pDecode(L, pqi->pData, pqi->utilid); 130 | pqi->pDecode = NULL; 131 | 132 | if (result < 1 || L == NULL) // if lua_state == NULL then we're cancelling 133 | { 134 | // indicator transaction is complete, do NOT create the userdata and do not call return callback 135 | pqi->pReturn = NULL; 136 | if (pqi->pWaitHandle != NULL) 137 | { 138 | DSS_waithandle_signal(pqi->pWaitHandle); 139 | pqi->pWaitHandle = NULL; 140 | } 141 | lua_pushinteger(L, g->QueueCount); // add count to results 142 | free(pqi); // No need to clear userdata, wasn't created yet in this case 143 | return 1; // Only count is returned 144 | } 145 | 146 | // remove any leftovers, keep only the results on the stack 147 | while (lua_gettop(L)>result) lua_remove(L, 1); 148 | 149 | lua_checkstack(L, 3); 150 | if (pqi->pReturn != NULL) 151 | { 152 | // Create userdata to reference the queueitem, because we have a return callback 153 | udata = (pQueueItem*)lua_newuserdata(L, sizeof(pQueueItem)); 154 | if (udata == NULL) 155 | { 156 | // memory allocation error, exit process here 157 | pqi->pReturn(NULL, pqi->pData, pqi->utilid, FALSE); // call with lua_State == NULL to have it cancelled 158 | DSS_waithandle_signal(pqi->pWaitHandle); 159 | free(pqi); 160 | lua_pushinteger(L, pqi->utilid->pGlobals->QueueCount); // add count to results 161 | // push an error to notify of failure??? 162 | return 1; // Only count is returned 163 | } 164 | // Set cross references 165 | *udata = pqi; // fill userdata with reference to queueitem 166 | pqi->udata = udata; // set reference to userdata in queueitem 167 | 168 | // attach metatable 169 | luaL_getmetatable(L, DSS_QUEUEITEM_MT); 170 | lua_setmetatable(L, -2); 171 | 172 | // store in userdata list 173 | pqi->pNext = g->UserdataStart; 174 | pqi->pPrevious = NULL; 175 | if (pqi->pNext != NULL) pqi->pNext->pPrevious = pqi; 176 | g->UserdataStart = pqi; 177 | 178 | // Move userdata (on top) to 2nd position, directly after the lua callback function 179 | if (lua_gettop(L) > 2 ) lua_insert(L, 2); 180 | result = result + 1; // 1 more result because we added the userdata 181 | } 182 | lua_createtable(L, result - 1, 0); // add a table 183 | if (lua_gettop(L) > 2 ) lua_insert(L, 2); // move it into 2nd pos 184 | while (lua_gettop(L) > 2) // migrate all callback arguments into the table 185 | { 186 | lua_rawseti(L, 2, lua_gettop(L)-2); 187 | } 188 | lua_pushinteger(L, g->QueueCount); // add count to results 189 | lua_insert(L,1); // move count to 1st position 190 | 191 | return 3; // count, callback, table cb arguments 192 | } 193 | 194 | // Return destructor 195 | // Execute the return callback, cleanup and finish process 196 | // 197 | // Note: if lua_state == NULL then the item will be cancelled 198 | int delivery_return(pQueueItem pqi, lua_State *L, BOOL garbage) 199 | { 200 | int result = 0; 201 | pglobalRecord g = pqi->utilid->pGlobals; 202 | 203 | // Move it off the userdata list 204 | if (pqi->pPrevious == NULL) 205 | { 206 | // its the first item in the list, so we need the global record to update the pointer there 207 | g->UserdataStart = pqi->pNext; 208 | if (pqi->pNext != NULL) pqi->pNext->pPrevious = NULL; 209 | } 210 | else 211 | { 212 | // its somewhere mid-list, just update 213 | pqi->pPrevious->pNext = pqi->pNext; 214 | if (pqi->pNext != NULL) pqi->pNext->pPrevious = pqi->pPrevious; 215 | } 216 | pqi->pNext = NULL; 217 | pqi->pPrevious = NULL; 218 | 219 | // Cleanup userdata 220 | (*(pqi->udata)) = NULL; // set reference in userdata to NULL, indicate its done 221 | if (L != NULL) lua_remove(L, 1); // remove the userdata from the stack 222 | 223 | // now execute callback, here the utility should release all resources 224 | result = pqi->pReturn(L, pqi->pData, pqi->utilid, garbage); 225 | 226 | // Cleanup queueitem 227 | pqi->pReturn = NULL; 228 | pqi->pData = NULL; 229 | if (pqi->pWaitHandle != NULL) 230 | { 231 | DSS_waithandle_signal(pqi->pWaitHandle); 232 | pqi->pWaitHandle = NULL; 233 | } 234 | 235 | // let go of own resources 236 | free(pqi); 237 | 238 | return result; 239 | } 240 | 241 | // Cancel destructor 242 | // Removes the item from the queue or userdata list and destroys it 243 | // will call the appropriate callback to release client resources 244 | void delivery_cancel(pQueueItem pqi) 245 | { 246 | if (pqi->udata != NULL) 247 | { 248 | // There is a userdata, so its on Lua side 249 | delivery_return(pqi, NULL, FALSE); 250 | } 251 | else 252 | { 253 | // No userdata, so must be in queue 254 | delivery_decode(pqi, NULL); 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /darksidesync/delivery.h: -------------------------------------------------------------------------------- 1 | #ifndef delivery_h 2 | #define delivery_h 3 | 4 | #include "darksidesync.h" 5 | #include "darksidesync_api.h" 6 | #include 7 | #include 8 | 9 | // Pointer type to delivery item 10 | //typedef struct qItem *pQueueItem; 11 | 12 | // Structure for storing data from an async callback in the queue 13 | // NOTE: while waiting for 'poll' to be called it will be in the queue, 14 | // while waiting for 'return' callback, it will be in a userdata 15 | //typedef struct qItem { 16 | // putilRecord utilid; // unique ID to utility 17 | // pDSS_waithandle pWaitHandle; // Wait handle to block thread while wait for return to be called 18 | // void* pData; // Data to be decoded 19 | // pQueueItem pNext; // Next item in queue/list 20 | // pQueueItem pPrevious; // Previous item in queue/list 21 | // pQueueItem* udata; // a userdata containing a pointer to this qItem 22 | // // API functions at the end, so casting of future versions can be done 23 | // DSS_decoder_1v0_t pDecode; // Pointer to the decode function, if NULL then it was already called 24 | // DSS_return_1v0_t pReturn; // Pointer to the return function 25 | // } QueueItem; 26 | 27 | // Methods, see code for more detailed comments 28 | // Create a new item and store it 29 | pQueueItem delivery_new(putilRecord utilid, DSS_decoder_1v0_t pDecode, DSS_return_1v0_t pReturn, void* pData, int* err); 30 | // Execute the poll/decode step, and move to userdata 31 | int delivery_decode(pQueueItem pqi, lua_State *L); 32 | // execute return step and destroy 33 | int delivery_return(pQueueItem pqi, lua_State *L, BOOL garbage); 34 | // cancel the item (either from queue or userdata) 35 | void delivery_cancel(pQueueItem pqi); 36 | 37 | #endif /* delivery_h */ 38 | -------------------------------------------------------------------------------- /darksidesync/dss.lua: -------------------------------------------------------------------------------- 1 | --------------------------------------------------------------------- 2 | -- This module contains the DarkSideSync-Lua side notification implementation based 3 | -- on the LuaSocket network library (this module is not required, it is just a quick start if 4 | -- you will be using LuaSocket with darksidesync). If you intend to use another 5 | -- network library, you need to rewrite/reimplement this code. If you do not want 6 | -- to use the UDP notifications, then this module is not necessary. 7 | -- Upon requiring this Lua module, it will load the additional 8 | -- C libraries (`darksidesync/core.so` or `darksidesync/core.dll`). 9 | -- @class module 10 | -- @name dss 11 | -- @copyright 2012-2013 Thijs Schreijer, DarkSideSync is free software under the MIT/X11 license 12 | -- @release Version 1.0, DarkSideSync. 13 | local socket = require("socket") 14 | local darksidesync = require("darksidesync") 15 | require("coxpcall") 16 | local skt, port 17 | 18 | local unpack = unpack or table.unpack -- 5.1/5.2 compatibility 19 | 20 | ---------------------------------------------------------------------------------------- 21 | -- creates and initializes the UDP socket to be listened on 22 | -- @return a luasocket.udp socket and the port number, or nil and an error message 23 | local createsocket = function() 24 | 25 | local trysocket = function(port) 26 | local status 27 | local skt, err = socket.udp() -- create a new socket 28 | if not skt then 29 | -- error occured 30 | return skt, err 31 | end 32 | status, err = skt:setsockname("localhost", port) -- only listen on LOCALHOST, loopback adapter 33 | if not status then 34 | -- error occured 35 | return nil, err 36 | end 37 | return skt -- return the created socket 38 | end 39 | 40 | local port = 50000 41 | local skt 42 | repeat 43 | skt = trysocket(port) 44 | if not skt then port = port + 1 end 45 | until skt or port > 50200 46 | if not skt then 47 | return nil, "Could not configure a UDP socket" 48 | end 49 | return skt, port 50 | end 51 | 52 | -- default error handler, see dss.seterrorhandler() below 53 | local _ehandler = function(msg) 54 | if msg then 55 | msg = tostring(msg) .. "\n" .. debug.traceback("DSS error: callback function had an error;\n") 56 | else 57 | msg = debug.traceback("DSS error: callback function had an error;\n") 58 | end 59 | print (msg) 60 | end 61 | local ehandler = _ehandler 62 | 63 | ---------------------------------------------------------------------------------------- 64 | -- reads incoming data on the socket, dismisses the data and calls poll() 65 | -- any data returned will have a first argument being the number of items 66 | -- remaining on the queue. And a second being a callback to be called with 67 | -- the remaining arguments 68 | local sockethandler = function(skt) 69 | -- collect data from socket, can be dismissed, won't be used 70 | skt:receive(8192) -- size not optional if using copas, add it to be sure 71 | -- now call poll() to collect the actual data in a new table with values 72 | local count, callback, args = darksidesync.poll() 73 | if count ~= -1 then 74 | if type(callback) ~= "function" then 75 | print (debug.traceback("error: the first argument returned should have been a Lua function!")) 76 | else 77 | -- now call the callback with the other arguments as parameters, in a protected (coxpcall) mode 78 | xpcall(function() callback(unpack(args)) end, ehandler) 79 | end 80 | end 81 | end 82 | 83 | -- define module table 84 | local dss = {} 85 | 86 | ---------------------------------------------------------------------------------------- 87 | -- Returns a socket where the helper module will be listening for incoming UDP 88 | -- signals that data is ready to be collected through `darksidesync.poll`. When data arrives it 89 | -- MUST be read, and next `darksidesync.poll` should be called (the sockethandler, see `gethandler`, will do this). 90 | -- If no socket was allocated yet, a new socket will be allocated. It will by default 91 | -- listen on `localhost` and try to pick a port number from 50000 and 50200. 92 | -- After allocating the socket, the DarkSideSync (C-side) function `darksidesync.setport` 93 | -- will be called to instruct the synchronization mechanism to send notifications on this port. 94 | -- @return Socket: Existing or newly created UDP socket 95 | -- @return Port: port number the socket is listening on 96 | -- @see gethandler 97 | -- @see darksidesync.poll 98 | -- @see darksidesync.setport 99 | dss.getsocket = function() 100 | if not skt then 101 | skt, port = createsocket() 102 | if skt then 103 | -- socket was created succesfully, now must tell my C side helper lib on what port 104 | -- I'm listening for incoming data 105 | darksidesync.setport(port) 106 | end 107 | end 108 | return skt, port 109 | end 110 | 111 | ----------------------------------------------------------------------------------------- 112 | -- Returns the socket handler function. This socket handler function will do a single 113 | -- read on the socket to empty the buffer, call `darksidesync.poll` for the asynchroneous 114 | -- data received, and call the appropriate callback with the arguments. So whenever 115 | -- a UDP notification packet is received, the socket handler function should be called 116 | -- to initiate the execution of the async callback. 117 | -- @return sockethandler function (the function returned requires a single argument; the socket to read from) 118 | -- @see darksidesync.poll 119 | -- @usage 120 | -- copas.addserver( -- assumes using the Copas scheduler 121 | -- dss.getsocket(), function(skt) 122 | -- skt = copas.wrap(skt) 123 | -- local hdlr = dss.gethandler() 124 | -- while true do 125 | -- hdlr(skt) 126 | -- end 127 | -- end) 128 | dss.gethandler = function() 129 | return sockethandler 130 | end 131 | 132 | ----------------------------------------------------------------------------------------- 133 | -- Returns the current queue size. 134 | -- @return number of elements currently waiting in the queue to be handled. 135 | dss.queuesize = function() 136 | return darksidesync.queuesize() 137 | end 138 | 139 | ----------------------------------------------------------------------------------------- 140 | -- Sets the error handler when calling the callback function returned from DarkSideSync. 141 | -- When the sockethandler function executes the callback, the function set though 142 | -- `seterrorhandler()` will be used as the error function on the `coxpcall`. 143 | -- The default errorhandler will print the error and a stack traceback. 144 | -- @param f the error handler function to be set (or `nil` to restore the default error handler) 145 | dss.seterrorhandler = function(f) 146 | assert(type(f) == "function", "The errorhandler must be a function.") 147 | ehandler = f or _ehandler 148 | end 149 | 150 | return dss 151 | -------------------------------------------------------------------------------- /darksidesync/dss_test.lua: -------------------------------------------------------------------------------- 1 | 2 | -- LUAEXIT tests 3 | 4 | -- darksidesync not loaded first, no delivery possibility 5 | -- call start before requiring darksidesync 6 | -- Expected; nil + error msg 7 | local dss = require("dss") 8 | local luaexit, result, err 9 | luaexit = require('luaexit') 10 | result, err = luaexit.start(function() end) 11 | print(result, err) 12 | assert(result == nil, "nil expected because darksidesync was not loaded yet") 13 | assert(type(err) == "string", "expected 2nd arg to be error string") 14 | print ("Ok\n") 15 | 16 | local darksidesync = require ("darksidesync") 17 | 18 | -- First argument to start must be a function 19 | -- call start without argument 20 | -- call start with a string argument 21 | -- Expected; nil + error msg 22 | result, err = luaexit.start() 23 | print(result, err) 24 | assert(result == nil, "nil expected because no callback function was provided") 25 | assert(type(err) == "string", "expected 2nd arg to be error string") 26 | print ("Ok\n") 27 | 28 | result, err = luaexit.start("this should fail") 29 | print(result, err) 30 | assert(result == nil, "nil expected because no callback function was provided, but a string") 31 | assert(type(err) == "string", "expected 2nd arg to be error string") 32 | print ("Ok\n") 33 | 34 | 35 | -- no callback set when calling poll 36 | -- 1) wait for signal 37 | -- 2) call stop 38 | -- 3) call poll 39 | -- Expected; nil + error msg 40 | 41 | local socket = require ("socket") 42 | local dss = require("dss") 43 | local skt, port = dss.getsocket() 44 | luaexit.start(function(...) print("LuaExit callback received: ", ...) end) 45 | print("Press CTRL+C to initiate a signal now (on windows cannot do this from within SCITE editor! use a terminal)") 46 | skt:receive(8192) -- wait for UDP packet 47 | luaexit.stop() 48 | result, err = darksidesync.poll() 49 | print(result, err) 50 | assert(result == nil, "nil expected because the callback was no longer available after stopping luaexit") 51 | assert(type(err) == "string", "expected 2nd arg to be error string") 52 | print ("Ok\n") 53 | 54 | 55 | 56 | 57 | 58 | -- DARKSIDESYNC tests 59 | 60 | -- GetPort should report the correct port number 61 | print ("checking getport function") 62 | assert(type(darksidesync.getport()) == "number", "Expected a numeric value for the port number") 63 | assert(darksidesync.getport() > 0, "Expected a numeric value greater than 0") 64 | print ("Ok\n") 65 | 66 | 67 | -- Start with a portnumber <0 or >65535 68 | -- call start with -5 69 | -- call start with 100000 70 | -- call start with no args 71 | -- call start with a string arg 72 | -- Expected; nil + error msg 73 | result, err = darksidesync.start(-5) 74 | print(result, err) 75 | assert(result == nil, "nil expected because a negative port number should not be accepted") 76 | assert(type(err) == "string", "expected 2nd arg to be error string") 77 | print ("Ok\n") 78 | result, err = darksidesync.start(100000) 79 | print(result, err) 80 | assert(result == nil, "nil expected because a port number greater than 65535 should not be accepted") 81 | assert(type(err) == "string", "expected 2nd arg to be error string") 82 | print ("Ok\n") 83 | result, err = darksidesync.start() 84 | print(result, err) 85 | assert(result == nil, "nil expected because a missing port number should not be accepted") 86 | assert(type(err) == "string", "expected 2nd arg to be error string") 87 | print ("Ok\n") 88 | print(result, err) 89 | assert(result == nil, "nil expected because a string argument as port number should not be accepted") 90 | assert(type(err) == "string", "expected 2nd arg to be error string") 91 | print ("Ok\n") 92 | 93 | print ("All tests passed!") 94 | -------------------------------------------------------------------------------- /darksidesync/locking.c: -------------------------------------------------------------------------------- 1 | #ifndef dss_locking_c 2 | #define dss_locking_c 3 | 4 | #include "locking.h" 5 | 6 | 7 | /* 8 | ** =============================================================== 9 | ** Locking functions 10 | ** =============================================================== 11 | */ 12 | 13 | // Initializes the mutex, returns 0 upon success, 1 otherwise 14 | int DSS_mutex_init(DSS_mutex_t* m) 15 | { 16 | #ifdef WIN32 17 | *m = CreateMutex( 18 | NULL, // default security attributes 19 | FALSE, // initially not owned 20 | NULL); // unnamed mutex 21 | if (*m == NULL) 22 | return 1; 23 | else 24 | return 0; 25 | #else 26 | // create attribute and set it to RECURSIVE as the mutex type 27 | pthread_mutexattr_t Attr; 28 | pthread_mutexattr_init(&Attr); 29 | pthread_mutexattr_settype(&Attr, PTHREAD_MUTEX_RECURSIVE); 30 | int r = pthread_mutex_init(m, &Attr); // return 0 upon success 31 | return r; 32 | #endif 33 | } 34 | 35 | // Destroy mutex 36 | void DSS_mutex_destroy(DSS_mutex_t* m) 37 | { 38 | #ifdef WIN32 39 | CloseHandle(*m); 40 | #else 41 | pthread_mutex_destroy(m); 42 | #endif 43 | } 44 | 45 | // Locks a mutex 46 | void DSS_mutex_lock(DSS_mutex_t* m) 47 | { 48 | #ifdef WIN32 49 | WaitForSingleObject(*m, INFINITE); 50 | #else 51 | pthread_mutex_lock(m); 52 | #endif 53 | } 54 | 55 | // Unlocks a mutex 56 | void DSS_mutex_unlock(DSS_mutex_t* m) 57 | { 58 | #ifdef WIN32 59 | ReleaseMutex(*m); 60 | #else 61 | pthread_mutex_unlock(m); 62 | #endif 63 | } 64 | 65 | #endif 66 | -------------------------------------------------------------------------------- /darksidesync/locking.h: -------------------------------------------------------------------------------- 1 | #ifndef dss_locking_h 2 | #define dss_locking_h 3 | 4 | #ifdef WIN32 5 | #include 6 | #define DSS_mutex_t HANDLE 7 | #else // Unix 8 | #include 9 | #define DSS_mutex_t pthread_mutex_t 10 | #endif 11 | 12 | int DSS_mutex_init(DSS_mutex_t* m); 13 | void DSS_mutex_destroy(DSS_mutex_t* m); 14 | void DSS_mutex_lock(DSS_mutex_t* m); 15 | void DSS_mutex_unlock(DSS_mutex_t* m); 16 | 17 | #endif /* dss_locking_h */ 18 | -------------------------------------------------------------------------------- /darksidesync/locking_sequence.txt: -------------------------------------------------------------------------------- 1 | proper luastate shutdown; 2 | ========================= 3 | will go through garbage collect at luastate shutdown 4 | 5 | - utility list is stored in library static data 6 | - data queue is stored in globaldata per luastate 7 | 8 | get globaldata from lua registry 9 | lock 10 | set status to stopping >> any utils will now get errors 11 | unlock >> allow any waiting utils to actually get the error 12 | unregister all utilities for this luastate (might require lock/unlock actions) 13 | --> there is no async access because access to globaldata is through utilrecord 14 | which were all just removed 15 | lock 16 | dispose of mutexes and sockets in globaldata 17 | remove references from registry to API and globaldata 18 | done! 19 | 20 | proper overall shutdown; 21 | ======================== 22 | when networkcount reaches 0, the static data must be disposed of 23 | lock 24 | clear everything 25 | unlock 26 | destroy lock --> the last two lines are not safe, another thread might claim the 27 | 28 | 29 | Accessing the global state data; 30 | =============================== 31 | When accessing it from a Lua callable function (anything with a lua_state 32 | parameter), then no locks will necessary when acquiring the global record because 33 | it will only be created or destroyed from a Lua thread, and those are single 34 | threaded by nature. NOTE: this applies only to collecting the record! NOT to 35 | modifying/reading it. Lock the global structure through its own lock before 36 | reading/writing, and unlock when done. 37 | 38 | When accessing the global data structure from a background worker, first lock 39 | the utillock mutex before accessing the utillist. 40 | Verify the utilid, then collect the global record. Lock the record using its 41 | own lock, only then release the utillock. Do the reading/writing and unlock 42 | when done. 43 | 44 | Lua registry globals; 45 | ==================== 46 | Registry 47 | | 48 | +- [DSS_REGISTRY_NAME] (table) Table with DSS api structs 49 | | +- [DSS_VERSION_KEY] (string) DSS version 50 | | +- [DSS_API_1v0_KEY] (lightuserdata) Pointer (type: pDSS_api_1v0_t) to api struct 51 | +- [DSS_GLOBALS_KEY] (userdata) Global DSS data per LuaState 52 | +- [DSS_GLOBALS_MT] (table) Metatable with __gc() for userdata cleanup 53 | -------------------------------------------------------------------------------- /darksidesync/udpsocket.c: -------------------------------------------------------------------------------- 1 | #ifndef dss_udpsocket_c 2 | #define dss_udpsocket_c 3 | 4 | #include 5 | #include 6 | #include "udpsocket.h" 7 | 8 | #define DSS_TARGET "localhost" 9 | 10 | #ifdef WIN32 11 | static WSADATA w; 12 | #endif 13 | 14 | 15 | /* 16 | ** =============================================================== 17 | ** Socket functions 18 | ** =============================================================== 19 | */ 20 | // Init network 21 | // return 1 upon success 22 | int udpsocket_networkInit() 23 | { 24 | int result = 1; 25 | #ifdef WIN32 26 | if (WSAStartup(0x0101, &w) != 0) result = 0; 27 | #endif 28 | return result; 29 | } 30 | 31 | // Shutdown network 32 | void udpsocket_networkStop() 33 | { 34 | #ifdef WIN32 35 | WSACleanup(); 36 | #endif 37 | } 38 | 39 | // Close socket 40 | void udpsocket_close(udpsocket_t s) 41 | { 42 | if (s.udpsock != INVALID_SOCKET) 43 | { 44 | #ifdef WIN32 45 | closesocket(s.udpsock); 46 | #else 47 | close(s.udpsock); 48 | #endif 49 | s.udpsock = INVALID_SOCKET; 50 | } 51 | } 52 | 53 | 54 | // Create a new socket 55 | // return socket struct, failed if member; udpsock == INVALID_SOCKET 56 | // Port == 0 always fails 57 | udpsocket_t udpsocket_new(int port) 58 | { 59 | udpsocket_t s; 60 | struct hostent *hp; 61 | s.udpsock = INVALID_SOCKET; 62 | 63 | if (port != 0) 64 | { 65 | #ifdef WIN32 66 | /* Open a datagram socket */ 67 | s.udpsock = socket(AF_INET, SOCK_DGRAM, 0); 68 | if (s.udpsock == INVALID_SOCKET) 69 | { 70 | return s; //failed to create socket 71 | } 72 | 73 | /* Clear out server struct */ 74 | memset((void *)&(s.receiver_addr), '\0', sizeof(struct sockaddr_in)); 75 | 76 | /* Set family and port */ 77 | s.receiver_addr.sin_family = AF_INET; 78 | s.receiver_addr.sin_port = htons(port); 79 | 80 | /* Get localhost address */ 81 | hp = gethostbyname(DSS_TARGET); 82 | 83 | /* Check for NULL pointer */ 84 | if (hp == NULL) 85 | { 86 | closesocket(s.udpsock); 87 | s.udpsock = INVALID_SOCKET; 88 | return s; // failed to resolve localhost name 89 | } 90 | 91 | /* Set target address */ 92 | s.receiver_addr.sin_addr.S_un.S_un_b.s_b1 = hp->h_addr_list[0][0]; 93 | s.receiver_addr.sin_addr.S_un.S_un_b.s_b2 = hp->h_addr_list[0][1]; 94 | s.receiver_addr.sin_addr.S_un.S_un_b.s_b3 = hp->h_addr_list[0][2]; 95 | s.receiver_addr.sin_addr.S_un.S_un_b.s_b4 = hp->h_addr_list[0][3]; 96 | 97 | #else 98 | 99 | // Create and return UDP socket for port number 100 | s.udpsock = socket(AF_INET, SOCK_DGRAM, 0); 101 | if (s.udpsock < 0) { 102 | s.udpsock = INVALID_SOCKET; 103 | return s; // report failure 104 | } 105 | 106 | // lookup 'localhost' 107 | s.receiver_addr.sin_family = AF_INET; 108 | hp = gethostbyname(DSS_TARGET); 109 | if (hp == 0) 110 | { 111 | // unkown host 112 | close(s.udpsock); 113 | s.udpsock = INVALID_SOCKET; 114 | return s; // report failure 115 | } 116 | 117 | // Set server and port 118 | bcopy((char *)hp->h_addr, 119 | (char *)&s.receiver_addr.sin_addr, 120 | hp->h_length); 121 | s.receiver_addr.sin_port = htons(port); 122 | 123 | #endif 124 | } 125 | 126 | return s; 127 | } 128 | 129 | // Sends packet, failure reported as 0, failure closes socket 130 | int udpsocket_send(udpsocket_t s, char *pData) 131 | { 132 | if (s.udpsock != INVALID_SOCKET) 133 | { 134 | //TODO: remove platform stuff; why the +1 for length at WIN32 and not the other? so far for copy-paste examples :-/ 135 | #ifdef WIN32 136 | /* Tranmsit data */ 137 | if (sendto(s.udpsock, pData, (int)strlen(pData) + 1, 0, (struct sockaddr *)&s.receiver_addr, sizeof(struct sockaddr_in)) == -1) 138 | { 139 | closesocket(s.udpsock); 140 | s.udpsock = INVALID_SOCKET; 141 | return 0; // report failure to send 142 | } 143 | #else 144 | int n; 145 | // Send string as UDP packet 146 | n = sendto(s.udpsock, &pData, 147 | strlen(pData), 0, (struct sockaddr*)&s.receiver_addr, 148 | sizeof(s.receiver_addr)); 149 | if (n < 0) 150 | { 151 | close(s.udpsock); 152 | s.udpsock = INVALID_SOCKET; 153 | return 0; // report failure 154 | } 155 | #endif 156 | return 1; // report success 157 | } 158 | return 0; // report failure (there was no socket) 159 | } 160 | 161 | #endif 162 | -------------------------------------------------------------------------------- /darksidesync/udpsocket.h: -------------------------------------------------------------------------------- 1 | #ifndef dss_udpsocket_h 2 | #define dss_udpsocket_h 3 | 4 | #ifdef WIN32 5 | #include 6 | #else 7 | #ifndef INVALID_SOCKET 8 | #define INVALID_SOCKET -1 // Define value for no valid socket 9 | #endif 10 | #include 11 | #include 12 | #include 13 | #endif 14 | 15 | // socket structure 16 | typedef struct udpsocket { 17 | #ifdef WIN32 18 | SOCKET udpsock; 19 | #else // Unix 20 | int udpsock; 21 | #endif 22 | struct sockaddr_in receiver_addr; 23 | } udpsocket_t; 24 | 25 | // Init / teardown network 26 | int udpsocket_networkInit(); 27 | void udpsocket_networkStop(); 28 | 29 | // Socket operations 30 | udpsocket_t udpsocket_new(int port); 31 | void udpsocket_close(udpsocket_t s); 32 | int udpsocket_send(udpsocket_t s, char *pData); 33 | 34 | #endif /* dss_udpsocket_h */ 35 | -------------------------------------------------------------------------------- /darksidesync/waithandle.c: -------------------------------------------------------------------------------- 1 | #ifndef dss_waithandle_c 2 | #define dss_waithandle_c 3 | 4 | #include 5 | #include 6 | #include 7 | #include "waithandle.h" 8 | 9 | /* 10 | ** =============================================================== 11 | ** Create a new waithandle 12 | ** =============================================================== 13 | */ 14 | // Returns NULL upon failure 15 | // initial state of the handle returned is 'reset' (Closed) 16 | pDSS_waithandle DSS_waithandle_create() 17 | { 18 | pDSS_waithandle wh = (pDSS_waithandle)malloc(sizeof(DSS_waithandle_t)); 19 | if (wh == NULL) 20 | { 21 | // error allocating memory 22 | return NULL; 23 | } 24 | #ifdef WIN32 25 | wh->semaphore = CreateSemaphore(NULL, 0, 1, NULL); 26 | if (wh->semaphore == NULL) { 27 | // failed initializing 28 | free(wh); 29 | return NULL; 30 | } 31 | #else 32 | int rt = sem_init(&(wh->semaphore), 0, 0); 33 | if (rt != 0 ) { 34 | // failed initializing 35 | free(wh); 36 | return NULL; 37 | } 38 | #endif 39 | DSS_waithandle_reset(wh); 40 | return wh; 41 | } 42 | 43 | /* 44 | ** =============================================================== 45 | ** Resets the waithandle (closes the gate) 46 | ** =============================================================== 47 | */ 48 | void DSS_waithandle_reset(pDSS_waithandle wh) 49 | { 50 | if (wh != NULL) { 51 | #ifdef WIN32 52 | // to reset, first release by 1, has no effect if already released 53 | ReleaseSemaphore(wh->semaphore,1, NULL); 54 | // now wait 1, effectively reducing to 0 and hence closing 55 | WaitForSingleObject(wh->semaphore, INFINITE); 56 | #else 57 | while (sem_trywait(&(wh->semaphore)) == 0); // wait (and reduce) until error (value = 0 and blocking) 58 | #endif 59 | } 60 | } 61 | 62 | /* 63 | ** =============================================================== 64 | ** Signals the waithandle (opens the gate) 65 | ** =============================================================== 66 | */ 67 | void DSS_waithandle_signal(pDSS_waithandle wh) 68 | { 69 | if (wh != NULL) { 70 | #ifdef WIN32 71 | ReleaseSemaphore(wh->semaphore, 1, NULL); 72 | #else 73 | sem_post(&(wh->semaphore)); 74 | #endif 75 | } 76 | } 77 | 78 | /* 79 | ** =============================================================== 80 | ** Waits for the waithandle to be signalled (tries passing the gate) 81 | ** =============================================================== 82 | */ 83 | void DSS_waithandle_wait(pDSS_waithandle wh) 84 | { 85 | if (wh != NULL) { 86 | #ifdef WIN32 87 | WaitForSingleObject(wh->semaphore, INFINITE); 88 | #else 89 | sem_wait(&(wh->semaphore)); 90 | #endif 91 | } 92 | } 93 | 94 | /* 95 | ** =============================================================== 96 | ** Destroys the waithandle, releases resources 97 | ** =============================================================== 98 | */ 99 | void DSS_waithandle_delete(pDSS_waithandle wh) 100 | { 101 | if (wh != NULL) { 102 | #ifdef WIN32 103 | // release before destroying 104 | ReleaseSemaphore(wh->semaphore, 1, NULL); 105 | CloseHandle(wh->semaphore); 106 | #else 107 | // release before destroying 108 | sem_post(&(wh->semaphore)); 109 | sem_destroy(&(wh->semaphore)); 110 | #endif 111 | // release resources 112 | free(wh); 113 | } 114 | } 115 | 116 | #endif 117 | -------------------------------------------------------------------------------- /darksidesync/waithandle.h: -------------------------------------------------------------------------------- 1 | #ifndef dss_waithandle_h 2 | #define dss_waithandle_h 3 | 4 | #ifdef WIN32 5 | #include 6 | #else 7 | #include 8 | #endif 9 | 10 | // waithandle structure 11 | typedef struct DSS_waithandle *pDSS_waithandle; 12 | typedef struct DSS_waithandle { 13 | #ifdef WIN32 14 | HANDLE semaphore; 15 | #else // Unix 16 | sem_t semaphore; 17 | #endif 18 | } DSS_waithandle_t; 19 | 20 | 21 | // WaitHandle operations 22 | pDSS_waithandle DSS_waithandle_create(); // creates a new waithandle, initial state is signalled 23 | void DSS_waithandle_reset(pDSS_waithandle wh); // resets status to blocking (closes the gate) 24 | void DSS_waithandle_signal(pDSS_waithandle wh); // sets status to signalled (opens the gate) 25 | void DSS_waithandle_wait(pDSS_waithandle wh); // blocks thread until handle gets signalled 26 | void DSS_waithandle_delete(pDSS_waithandle wh); // destroys the waithandle 27 | 28 | #endif /* dss_waithandle_h */ 29 | -------------------------------------------------------------------------------- /doc/background_worker_lib_spec_0v4.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tieske/DarkSideSync/060cfc55774ea0a5b42361b87b2ec7a3ff2ea7e2/doc/background_worker_lib_spec_0v4.pdf -------------------------------------------------------------------------------- /doc/index.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | DarkSideSync reference 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 |
16 |
17 |
18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 43 | 44 |
45 | 46 | 47 | 48 |

Background thread synchronization module for Lua

49 | 50 |

Modules

51 | 52 | 53 | 54 | 56 | 57 | 58 | 59 | 62 | 63 |
darksidesyncDarkSideSync is a Lua helper module for asynchroneous callbacks from 55 | other libraries.
dssThis module contains the DarkSideSync-Lua side notification implementation based 60 | on the LuaSocket network library (this module is not required, it is just a quick start if 61 | you will be using LuaSocket with darksidesync).
64 |

Topics

65 | 66 | 67 | 68 | 69 | 70 |
readme.md
71 | 72 |
73 |
74 |
75 | generated by LDoc 1.3 76 |
77 |
78 | 79 | 80 | -------------------------------------------------------------------------------- /doc/ldoc.css: -------------------------------------------------------------------------------- 1 | /* BEGIN RESET 2 | 3 | Copyright (c) 2010, Yahoo! Inc. All rights reserved. 4 | Code licensed under the BSD License: 5 | http://developer.yahoo.com/yui/license.html 6 | version: 2.8.2r1 7 | */ 8 | html { 9 | color: #000; 10 | background: #FFF; 11 | } 12 | body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,button,textarea,p,blockquote,th,td { 13 | margin: 0; 14 | padding: 0; 15 | } 16 | table { 17 | border-collapse: collapse; 18 | border-spacing: 0; 19 | } 20 | fieldset,img { 21 | border: 0; 22 | } 23 | address,caption,cite,code,dfn,em,strong,th,var,optgroup { 24 | font-style: inherit; 25 | font-weight: inherit; 26 | } 27 | del,ins { 28 | text-decoration: none; 29 | } 30 | li { 31 | list-style: bullet; 32 | margin-left: 20px; 33 | } 34 | caption,th { 35 | text-align: left; 36 | } 37 | h1,h2,h3,h4,h5,h6 { 38 | font-size: 100%; 39 | font-weight: bold; 40 | } 41 | q:before,q:after { 42 | content: ''; 43 | } 44 | abbr,acronym { 45 | border: 0; 46 | font-variant: normal; 47 | } 48 | sup { 49 | vertical-align: baseline; 50 | } 51 | sub { 52 | vertical-align: baseline; 53 | } 54 | legend { 55 | color: #000; 56 | } 57 | input,button,textarea,select,optgroup,option { 58 | font-family: inherit; 59 | font-size: inherit; 60 | font-style: inherit; 61 | font-weight: inherit; 62 | } 63 | input,button,textarea,select {*font-size:100%; 64 | } 65 | /* END RESET */ 66 | 67 | body { 68 | margin-left: 1em; 69 | margin-right: 1em; 70 | font-family: arial, helvetica, geneva, sans-serif; 71 | background-color: #ffffff; margin: 0px; 72 | } 73 | 74 | code, tt { font-family: monospace; } 75 | span.parameter { font-family:monospace; } 76 | span.parameter:after { content:":"; } 77 | span.types:before { content:"("; } 78 | span.types:after { content:")"; } 79 | .type { font-weight: bold; font-style:italic } 80 | 81 | body, p, td, th { font-size: .95em; line-height: 1.2em;} 82 | 83 | p, ul { margin: 10px 0 0 0px;} 84 | 85 | strong { font-weight: bold;} 86 | 87 | em { font-style: italic;} 88 | 89 | h1 { 90 | font-size: 1.5em; 91 | margin: 0 0 20px 0; 92 | } 93 | h2, h3, h4 { margin: 15px 0 10px 0; } 94 | h2 { font-size: 1.25em; } 95 | h3 { font-size: 1.15em; } 96 | h4 { font-size: 1.06em; } 97 | 98 | a:link { font-weight: bold; color: #004080; text-decoration: none; } 99 | a:visited { font-weight: bold; color: #006699; text-decoration: none; } 100 | a:link:hover { text-decoration: underline; } 101 | 102 | hr { 103 | color:#cccccc; 104 | background: #00007f; 105 | height: 1px; 106 | } 107 | 108 | blockquote { margin-left: 3em; } 109 | 110 | ul { list-style-type: disc; } 111 | 112 | p.name { 113 | font-family: "Andale Mono", monospace; 114 | padding-top: 1em; 115 | } 116 | 117 | pre.example { 118 | background-color: rgb(245, 245, 245); 119 | border: 1px solid silver; 120 | padding: 10px; 121 | margin: 10px 0 10px 0; 122 | font-family: "Andale Mono", monospace; 123 | font-size: .85em; 124 | } 125 | 126 | pre { 127 | background-color: rgb(245, 245, 245); 128 | border: 1px solid silver; 129 | padding: 10px; 130 | margin: 10px 0 10px 0; 131 | overflow: auto; 132 | font-family: "Andale Mono", monospace; 133 | } 134 | 135 | 136 | table.index { border: 1px #00007f; } 137 | table.index td { text-align: left; vertical-align: top; } 138 | 139 | #container { 140 | margin-left: 1em; 141 | margin-right: 1em; 142 | background-color: #f0f0f0; 143 | } 144 | 145 | #product { 146 | text-align: center; 147 | border-bottom: 1px solid #cccccc; 148 | background-color: #ffffff; 149 | } 150 | 151 | #product big { 152 | font-size: 2em; 153 | } 154 | 155 | #main { 156 | background-color: #f0f0f0; 157 | border-left: 2px solid #cccccc; 158 | } 159 | 160 | #navigation { 161 | float: left; 162 | width: 18em; 163 | vertical-align: top; 164 | background-color: #f0f0f0; 165 | overflow: visible; 166 | } 167 | 168 | #navigation h2 { 169 | background-color:#e7e7e7; 170 | font-size:1.1em; 171 | color:#000000; 172 | text-align: left; 173 | padding:0.2em; 174 | border-top:1px solid #dddddd; 175 | border-bottom:1px solid #dddddd; 176 | } 177 | 178 | #navigation ul 179 | { 180 | font-size:1em; 181 | list-style-type: none; 182 | margin: 1px 1px 10px 1px; 183 | } 184 | 185 | #navigation li { 186 | text-indent: -1em; 187 | display: block; 188 | margin: 3px 0px 0px 22px; 189 | } 190 | 191 | #navigation li li a { 192 | margin: 0px 3px 0px -1em; 193 | } 194 | 195 | #content { 196 | margin-left: 18em; 197 | padding: 1em; 198 | width: 700px; 199 | border-left: 2px solid #cccccc; 200 | border-right: 2px solid #cccccc; 201 | background-color: #ffffff; 202 | } 203 | 204 | #about { 205 | clear: both; 206 | padding: 5px; 207 | border-top: 2px solid #cccccc; 208 | background-color: #ffffff; 209 | } 210 | 211 | @media print { 212 | body { 213 | font: 12pt "Times New Roman", "TimeNR", Times, serif; 214 | } 215 | a { font-weight: bold; color: #004080; text-decoration: underline; } 216 | 217 | #main { 218 | background-color: #ffffff; 219 | border-left: 0px; 220 | } 221 | 222 | #container { 223 | margin-left: 2%; 224 | margin-right: 2%; 225 | background-color: #ffffff; 226 | } 227 | 228 | #content { 229 | padding: 1em; 230 | background-color: #ffffff; 231 | } 232 | 233 | #navigation { 234 | display: none; 235 | } 236 | pre.example { 237 | font-family: "Andale Mono", monospace; 238 | font-size: 10pt; 239 | page-break-inside: avoid; 240 | } 241 | } 242 | 243 | table.module_list { 244 | border-width: 1px; 245 | border-style: solid; 246 | border-color: #cccccc; 247 | border-collapse: collapse; 248 | } 249 | table.module_list td { 250 | border-width: 1px; 251 | padding: 3px; 252 | border-style: solid; 253 | border-color: #cccccc; 254 | } 255 | table.module_list td.name { background-color: #f0f0f0; ; min-width: 200px; } 256 | table.module_list td.summary { width: 100%; } 257 | 258 | 259 | table.function_list { 260 | border-width: 1px; 261 | border-style: solid; 262 | border-color: #cccccc; 263 | border-collapse: collapse; 264 | } 265 | table.function_list td { 266 | border-width: 1px; 267 | padding: 3px; 268 | border-style: solid; 269 | border-color: #cccccc; 270 | } 271 | table.function_list td.name { background-color: #f0f0f0; ; min-width: 200px; } 272 | table.function_list td.summary { width: 100%; } 273 | 274 | dl.table dt, dl.function dt {border-top: 1px solid #ccc; padding-top: 1em;} 275 | dl.table dd, dl.function dd {padding-bottom: 1em; margin: 10px 0 0 20px;} 276 | dl.table h3, dl.function h3 {font-size: .95em;} 277 | 278 | /* stop sublists from having initial vertical space */ 279 | ul ul { margin-top: 0px; } 280 | ol ul { margin-top: 0px; } 281 | ol ol { margin-top: 0px; } 282 | ul ol { margin-top: 0px; } 283 | 284 | /* styles for prettification of source */ 285 | pre .comment { color: #558817; } 286 | pre .constant { color: #a8660d; } 287 | pre .escape { color: #844631; } 288 | pre .keyword { color: #2239a8; font-weight: bold; } 289 | pre .library { color: #0e7c6b; } 290 | pre .marker { color: #512b1e; background: #fedc56; font-weight: bold; } 291 | pre .string { color: #a8660d; } 292 | pre .number { color: #f8660d; } 293 | pre .operator { color: #2239a8; font-weight: bold; } 294 | pre .preprocessor, pre .prepro { color: #a33243; } 295 | pre .global { color: #800080; } 296 | pre .prompt { color: #558817; } 297 | pre .url { color: #272fc2; text-decoration: underline; } 298 | -------------------------------------------------------------------------------- /doc/modules/DSS.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | DarkSideSync reference 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 |
16 |
17 |
18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 50 | 51 |
52 | 53 |

Module dss

54 | 55 |

This module contains the DarkSideSync-Lua side notification implementation based 56 | on the LuaSocket network library (this module is not required, it is just a quick start if 57 | you will be using LuaSocket with darksidesync).

58 |

If you intend to use another 59 | network library, you need to rewrite/reimplement this code. If you do not want 60 | to use the UDP notifications, then this module is not necessary. 61 | Upon requiring this Lua module, it will load the additional 62 | C libraries (darksidesync/core.so or darksidesync/core.dll).

63 |

Info:

64 |
    65 |
  • Release: Version 1.0, DarkSideSync.
  • 66 |
  • Copyright: 2012-2013 Thijs Schreijer, DarkSideSync is free software under the MIT/X11 license
  • 67 |
68 | 69 | 70 |

Functions

71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 81 | 82 | 83 | 84 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 |
createsocket ()creates and initializes the UDP socket to be listened on
sockethandler (skt)reads incoming data on the socket, dismisses the data and calls poll() 79 | any data returned will have a first argument being the number of items 80 | remaining on the queue.
getsocket ()Returns a socket where the helper module will be listening for incoming UDP 85 | signals that data is ready to be collected through darksidesync.poll .
gethandler ()Returns the socket handler function.
queuesize ()Returns the current queue size.
seterrorhandler (f)Sets the error handler when calling the callback function returned from DarkSideSync.
100 | 101 |
102 |
103 | 104 | 105 |

Functions

106 |
107 |
108 | 109 | createsocket () 110 |
111 |
112 | creates and initializes the UDP socket to be listened on 113 | 114 | 115 |

Returns:

116 |
    117 | 118 | a luasocket.udp socket and the port number, or nil and an error message 119 |
120 | 121 | 122 | 123 | 124 |
125 |
126 | 127 | sockethandler (skt) 128 |
129 |
130 | reads incoming data on the socket, dismisses the data and calls poll() 131 | any data returned will have a first argument being the number of items 132 | remaining on the queue. And a second being a callback to be called with 133 | the remaining arguments 134 | 135 |

Parameters:

136 |
    137 |
  • skt 138 | collect data from socket, can be dismissed, won't be used
  • 139 |
140 | 141 | 142 | 143 | 144 | 145 |
146 |
147 | 148 | getsocket () 149 |
150 |
151 | Returns a socket where the helper module will be listening for incoming UDP 152 | signals that data is ready to be collected through darksidesync.poll . When data arrives it 153 | MUST be read, and next darksidesync.poll should be called (the sockethandler, see gethandler , will do this). 154 | If no socket was allocated yet, a new socket will be allocated. It will by default 155 | listen on localhost and try to pick a port number from 50000 and 50200. 156 | After allocating the socket, the DarkSideSync (C-side) function darksidesync.setport 157 | will be called to instruct the synchronization mechanism to send notifications on this port. 158 | 159 | 160 |

Returns:

161 |
    162 |
  1. 163 | Socket: Existing or newly created UDP socket
  2. 164 |
  3. 165 | Port: port number the socket is listening on
  4. 166 |
167 | 168 | 169 |

see also:

170 | 175 | 176 | 177 |
178 |
179 | 180 | gethandler () 181 |
182 |
183 | Returns the socket handler function. This socket handler function will do a single 184 | read on the socket to empty the buffer, call darksidesync.poll for the asynchroneous 185 | data received, and call the appropriate callback with the arguments. So whenever 186 | a UDP notification packet is received, the socket handler function should be called 187 | to initiate the execution of the async callback. 188 | 189 | 190 |

Returns:

191 |
    192 | 193 | sockethandler function (the function returned requires a single argument; the socket to read from) 194 |
195 | 196 | 197 |

see also:

198 | 201 | 202 |

Usage:

203 |
    204 |
    205 |  copas.addserver(       -- assumes using the Copas scheduler
    206 |    dss.getsocket(), function(skt)
    207 |      skt = copas.wrap(skt)
    208 |      local hdlr = dss.gethandler()
    209 |      while true do
    210 |        hdlr(skt)
    211 |      end
    212 |    end)
    213 |
214 | 215 |
216 |
217 | 218 | queuesize () 219 |
220 |
221 | Returns the current queue size. 222 | 223 | 224 |

Returns:

225 |
    226 | 227 | number of elements currently waiting in the queue to be handled. 228 |
229 | 230 | 231 | 232 | 233 |
234 |
235 | 236 | seterrorhandler (f) 237 |
238 |
239 | Sets the error handler when calling the callback function returned from DarkSideSync. 240 | When the sockethandler function executes the callback, the function set though 241 | seterrorhandler() will be used as the error function on the coxpcall. 242 | The default errorhandler will print the error and a stack traceback. 243 | 244 |

Parameters:

245 |
    246 |
  • f 247 | the error handler function to be set (or nil to restore the default error handler)
  • 248 |
249 | 250 | 251 | 252 | 253 | 254 |
255 |
256 | 257 | 258 |
259 |
260 |
261 | generated by LDoc 1.3 262 |
263 |
264 | 265 | 266 | -------------------------------------------------------------------------------- /doc/modules/darksidesync.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | DarkSideSync reference 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 |
16 |
17 |
18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 51 | 52 |
53 | 54 |

Module darksidesync

55 | 56 |

DarkSideSync is a Lua helper module for asynchroneous callbacks from 57 | other libraries.

58 |

Lua is single-threaded by nature and hence working with 59 | multithreaded libraries is a complex matter. DarkSideSync aim is to make 60 | using asynchroneous libraries (managing their own threadpools) simple.

61 | 62 |

DarkSideSync takes away the complexity of messages queues, 63 | locking, synchronization, etc. because it implements them once and has a 64 | thread safe API to perform all those tasks, and notify Lua of incoming 65 | threads/data. It is a regular Lua module (C library) that can be loaded 66 | from Lua (no C-side dependencies/linking for any libraries using DarkSideSync) 67 | and it supports many libraries to consume its services simultaneously.

68 | 69 |

Check here for an overview.

70 | 71 |

It can only work with libraries designed to work with DarkSideSync. Check 72 | out the library source, specifically darksidesync_api.h on how 73 | to do that. Additionally use darksidesync_aux.c to get up and 74 | running with DarkSideSync quickly (just an include of this file will get 75 | you 95% done).

76 | 77 |

To use the DarkSideSync library from Lua there are 2 options

78 | 79 |
    80 |
  1. do not use notifications, but regularly call poll to check for incoming data
  2. 81 |
  3. use the UDP notification mechanism (a LuaSocket implementation is available in the dss module).
  4. 82 |
83 | 84 |

The latter has UDP networking overhead but has some advantages; works with any network library and 85 | allows the application to 'go to sleep' in a network select() call. Additionally a UDP socket 86 | has the advantage (over a filehandle) that it works on (almost) every platform. 87 | In cases with a high number of callbacks the polling method is considered the better solution.

88 | 89 |

If you'll be using LuaSocket, then you can probably use the dss module which has a LuaSocket specific 90 | abstraction on top of this darksidesync core module.

91 |

Info:

92 |
    93 |
  • Release: Version 1.0, DarkSideSync.
  • 94 |
  • Copyright: 2012-2013 Thijs Schreijer, DarkSideSync is free software under the MIT/X11 license
  • 95 |
96 | 97 | 98 |

C-side DarkSideSync API

99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 |
darksidesync_api.h ()Contains the complete C-side API.
darksidesync_aux.c ()Contains the core client implementation code.
109 |

Lua-side DarkSideSync API

110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 |
setport (port)Sets the UDP port for notifications.
getport ()Returns the UDP port currently in use for notifications.
poll ()Gets the next item from the darksidesync queue.
queuesize ()Returns the current size of the darksidesync queue.
waitingthread_callback (...)Callback function to set the results of an async callback.
132 | 133 |
134 |
135 | 136 | 137 |

C-side DarkSideSync API

138 | C-side DarkSideSync API. 139 | This section covers the darksidesync API from the C-side. It is not separately documented, but only in 140 | the code files. 141 |
142 |
143 | 144 | darksidesync_api.h () 145 |
146 |
147 | Contains the complete C-side API. 148 | See API header file darksidesync_api.h. 149 | 150 | 151 | 152 | 153 | 154 | 155 |
156 |
157 | 158 | darksidesync_aux.c () 159 |
160 |
161 | Contains the core client implementation code. 162 | An implementation of the C-side API (darksidesync_aux.c 163 | and darksidesync_aux.h) is available. 164 | This implementation should suffice for most usecases. Just copy the file into your project and include it (make sure to read the notes on linking 165 | in darksidesync_api.h ). 166 | 167 | 168 | 169 | 170 | 171 | 172 |
173 |
174 |

Lua-side DarkSideSync API

175 | Lua-side DarkSideSync API. 176 | This section covers the darksidesync API from the Lua-side 177 |
178 |
179 | 180 | setport (port) 181 |
182 |
183 | Sets the UDP port for notifications. For every item delivered in the 184 | darksidesync queue a notification will be sent. The IP address the notification 185 | will be send to will always be localhost (loopback adapter). 186 | 187 |

Parameters:

188 |
    189 |
  • port 190 | UDP port number to use for notification packets. A value from 0 to 65535, where 0 will disable notifications.
  • 191 |
192 | 193 |

Returns:

194 |
    195 | 196 | 1 if successfull, or nil + error msg if it failed 197 |
198 | 199 | 200 |

see also:

201 | 204 | 205 | 206 |
207 |
208 | 209 | getport () 210 |
211 |
212 | Returns the UDP port currently in use for notifications. 213 | 214 | 215 |

Returns:

216 |
    217 | 218 | UDP portnumber in use (1-65535), or 0 if notifications are disabled 219 |
220 | 221 | 222 |

see also:

223 | 226 | 227 | 228 |
229 |
230 | 231 | poll () 232 |
233 |
234 | Gets the next item from the darksidesync queue. 235 | If you use the UDP notifications, you MUST also read from the UDP socket to 236 | clear the received packet from the socket buffer.

237 | 238 |

NOTE: some of the return values will be generated by 239 | the client library (that is using darksidesync to get its data delivered to the Lua state) and other 240 | return values will be inserted by darksidesync. 241 | 242 | 243 |

Returns:

244 |
    245 |
  1. 246 | (by DSS) queuesize of remaining items (or -1 if there was nothing on the queue to begin with)
  2. 247 |
  3. 248 | (by client) Lua callback function to handle the data
  4. 249 |
  5. 250 | Table with arguments for the Lua callback, this contains (by client library) any other parameters as delivered by the async callback. Optionally, if the async thread requires a result to be returned, a waitingthread_callback function (by DSS) is inserted at position 1 (but only if the async callback expects Lua to deliver a result, in this case the async callback thread will be blocked until the waitingthread_callback is called)
  6. 251 |
252 | 253 | 254 | 255 |

Usage:

256 |
    257 |
    258 | local runcallbacks()
    259 |   local count, callback, args = darksidesync.poll()
    260 |   if count == -1 then return end	-- queue was empty, nothing to do
    261 |   callback(unpack(args))            -- execute callback
    262 |   if count > 0 then
    263 |     print("there is more to do; " .. tostring(count) .. " items are still in the queue.")
    264 |   else
    265 |     print("We're done for now.")
    266 |   end
    267 | end
    268 |
269 | 270 |
271 |
272 | 273 | queuesize () 274 |
275 |
276 | Returns the current size of the darksidesync queue. 277 | 278 | 279 |

Returns:

280 |
    281 | 282 | number of items in the queue 283 |
284 | 285 | 286 | 287 | 288 |
289 |
290 | 291 | waitingthread_callback (...) 292 |
293 |
294 | Callback function to set the results of an async callback. The 'waiting-thread' callback is collected from 295 | the poll method in case a background thread is blocked and waiting for a result. 296 | Call this function with the results to return to the async callback. 297 | 298 |

Parameters:

299 |
    300 |
  • ... 301 | parameters to be delivered to the async callback. This depends on what the client library expects
  • 302 |
303 | 304 |

Returns:

305 |
    306 | 307 | depends on client library implementation 308 |
309 | 310 | 311 |

see also:

312 | 315 | 316 | 317 |
318 |
319 | 320 | 321 |
322 |
323 |
324 | generated by LDoc 1.3 325 |
326 |
327 | 328 | 329 | -------------------------------------------------------------------------------- /doc/topics/readme.md.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | DarkSideSync reference 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 |
16 |
17 |
18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 46 | 47 |
48 | 49 |

Topic readme.md

50 | 51 | 52 |

DarkSideSync

53 | 54 |

Usecase

55 |

I wanted to create a lua binding for a library. The library uses callbacks to report on its async operations. The main issue however was that the library created its own threadpool and the callbacks could be executed on any one of those. And that, sooner or later, will wreak havoc with Lua's single threaded nature.

56 | 57 |

So while Lua's main thread is doing its Lua thing, it needs to be notified that there is some other thread that tries to deliver some result it needs to handle.

58 | 59 |

To support this DarkSideSync (DSS) was created, DSS has no use on its own, it will only support other libraries in handling async callbacks.

60 | 61 |

Main principle

62 |

Whenever a background thread has some data to deliver it will call a DSS method to temporarily store it in a queue. Whenever Lua has time to poll for data from this queue, the background library will get a direct call from DSS to deliver its data to Lua.

63 | 64 |

It supports async callbacks that just deliver data (the callback thread does not get blocked) and async callbacks that also expect a result (in this case the thread gets blocked until the Lua side thread has delivered the results)

65 | 66 |

There is an optional notification using a UDP packet, which is an easy way to wake up Lua from a select() network method.

67 | 68 |

Reference

69 |

Documentation can be found in the repository and on the GitHub page, a general description and some diagrams are also available.

70 | 71 |

Positive

72 | 73 |
    74 |
  • It is very generic and cross platform. Because DSS takes care of threads, locks and (optionally) sockets, a library binding for an async library may require no platform specific code and still work cross-platform
  • 75 |
  • It is setup as a separate library, loaded from Lua, no C links. It, sort of, dynamically extends the Lua C api
  • 76 |
  • Supports async callbacks that only deliver data (the callback thread is not blocked)
  • 77 |
  • Supports async callbacks that need a response (the callback thread is blocked until the Lua side response has been delivered)
  • 78 |
  • Supports multiple async background libraries simultaneously
  • 79 |
  • Supports multiple concurrent Lua states
  • 80 |
  • It has been setup with the intend to support multiple versions of the DSS API, so in the future multiple background libraries can use a single DSS library, while talking to different versions of the API
  • 81 |
  • The notification is also platform independent and even network library independent (eg. not bound to LuaSocket) any network library supporting UDP will do (this is the reason for not using file descriptors or pipes)
  • 82 |
83 | 84 |

Negative

85 | 86 |
    87 |
  • Notification using UDP packets requires some overhead, so for a very high number of callbacks it might be better to only use polling
  • 88 |
89 | 90 | 91 |

Copyright & License

92 |

Copyright 2012-2013, Thijs Schreijer.

93 | 94 |

License is the same as Lua 5.1; MIT license.

95 | 96 |

The name

97 |

Lua = moon, dark side of the moon, externally managed threads that Lua cannot reach, you get it...

98 | 99 |

Changes

100 | 101 |

1.0 13-may-2013

102 | 103 |
    104 |
  • Initial released version
  • 105 |
106 | 107 | 108 |
109 |
110 |
111 | generated by LDoc 1.3 112 |
113 |
114 | 115 | 116 | -------------------------------------------------------------------------------- /generate_docs.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | ldoc . 3 | pause 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /luaexit/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | *.user -------------------------------------------------------------------------------- /luaexit/install.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | echo off 3 | REM =================================================== 4 | REM This batch files copies the build output to the Lua 5 | REM for Windows directory, set the path below correct 6 | REM =================================================== 7 | SET T_LUAPATH=C:\Users\Public\lua\5.1 8 | 9 | echo Copying file 'luaexit.dll' 10 | copy "..\debug\luaexit.dll" "%T_LUAPATH%\clibs" 11 | 12 | echo Copying file 'luaexit_test.lua' 13 | copy "luaexit_test.lua" "%T_LUAPATH%\lua" 14 | -------------------------------------------------------------------------------- /luaexit/luaexit.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "luaexit.h" 5 | #include "darksidesync.h" 6 | #include "darksidesync_aux.h" 7 | 8 | static volatile int CallbackReference = LUA_NOREF; 9 | static void* DSSutilid; 10 | 11 | // TODO: for windows implement the following; 12 | // SetConsoleCtrlHandler http://msdn.microsoft.com/en-us/library/windows/desktop/ms686016(v=vs.85).aspx 13 | // RegisterServiceCtrlHandler http://msdn.microsoft.com/en-us/library/windows/desktop/ms685054(v=vs.85).aspx 14 | 15 | // forward definitions 16 | int L_stop (lua_State *L); 17 | 18 | /* 19 | ** =============================================================== 20 | ** Signal handling code 21 | ** =============================================================== 22 | */ 23 | 24 | // GC procedure to cleanup stuff 25 | 26 | static int LuaExit_exit(lua_State *L) 27 | { 28 | #ifdef _DEBUG 29 | OutputDebugStringA("LuaExit: unloading started...\n"); 30 | #endif 31 | 32 | L_stop(L); 33 | DSS_shutdown(L, NULL); 34 | return 0; 35 | 36 | #ifdef _DEBUG 37 | OutputDebugStringA("LuaExit: unloading completed\n"); 38 | #endif 39 | } 40 | 41 | void DSScancel(void* utilid) 42 | { 43 | DSS_shutdown(NULL, utilid); 44 | } 45 | 46 | // Decodes data and puts it on the Lua stack 47 | // pData is always NULL in this case, because we only handle 48 | // SIGTERM & SIGINT signal, so push constant string 49 | // @returns; as with Lua function, return number of args on the stack to return 50 | int signalDecoder (lua_State *L, void *pData, void *utilid) 51 | { 52 | if (L == NULL) 53 | { 54 | // element is being cancelled, do nothing 55 | 56 | } 57 | lua_settop(L, 0); 58 | if (CallbackReference == LUA_NOREF) 59 | { 60 | lua_pushnil(L); 61 | lua_pushstring(L, "luasignal; No callback function set"); 62 | return 2; 63 | } 64 | else 65 | { 66 | // TODO: execute callback from here, no more Lua side code 67 | lua_rawgeti(L, LUA_REGISTRYINDEX, CallbackReference); 68 | lua_pushstring(L, "SIGTERM"); // TODO: update this, its more than SIGTERM 69 | return 2; 70 | } 71 | } 72 | 73 | void signalHandler(int sigNum) 74 | { 75 | DSS_decoder_1v0_t DecodeFunc; 76 | signal(sigNum, SIG_IGN); // Temporarily ignore signals 77 | 78 | DecodeFunc = &signalDecoder; 79 | // deliver signal to DarkSideSync, no data included 80 | DSSapi->deliver(DSSutilid, DecodeFunc, NULL, NULL); // TODO: this is probably not safe in a signal handler!!!!! 81 | 82 | signal(sigNum, signalHandler); // Set handler again 83 | } 84 | 85 | /* 86 | ** =============================================================== 87 | ** Lua API 88 | ** =============================================================== 89 | */ 90 | // Lua function to start the library 91 | // Params; 1, function to use as callback for the signal 92 | int L_start(lua_State *L) 93 | { 94 | // anchor provided callback function in register 95 | if (lua_gettop(L) >= 1 && lua_isfunction(L,1)) 96 | { 97 | lua_settop(L,1); 98 | CallbackReference = luaL_ref(L, LUA_REGISTRYINDEX); 99 | } 100 | else 101 | { 102 | // not a function, error 103 | lua_settop(L,0); 104 | lua_pushnil(L); 105 | lua_pushstring(L, "Expected single argument of type function, to be used as callback"); 106 | return 2; 107 | } 108 | 109 | // install signal handlers 110 | signal(SIGTERM, signalHandler); 111 | signal(SIGINT, signalHandler); 112 | lua_settop(L,0); 113 | lua_pushinteger(L, 1); // report success 114 | return 1; 115 | }; 116 | 117 | // Lua function to stop the library and clear the callback 118 | int L_stop (lua_State *L) 119 | { 120 | signal(SIGTERM, SIG_DFL); // set to default handler 121 | // Clear callback function from register 122 | luaL_unref(L, LUA_REGISTRYINDEX, CallbackReference); 123 | CallbackReference = LUA_NOREF; 124 | // set results 125 | lua_settop(L,0); 126 | lua_pushinteger(L, 1); // report success 127 | return 0; 128 | }; 129 | 130 | /* 131 | ** =============================================================== 132 | ** Library initialization 133 | ** =============================================================== 134 | */ 135 | static const struct luaL_Reg LuaExit[] = { 136 | {"start",L_start}, 137 | {"stop",L_stop}, 138 | {NULL,NULL} 139 | }; 140 | 141 | EXPORT_API int luaopen_luaexit(lua_State *L){ 142 | 143 | DSS_cancel_1v0_t CancelFunc = &DSScancel; 144 | 145 | #ifdef _DEBUG 146 | OutputDebugStringA("LuaExit: LuaOpen started...\n"); 147 | #endif 148 | // Create userdata 149 | lua_newuserdata(L, sizeof(void*)); 150 | // Create a metatable to GC the global data upon exit 151 | luaL_newmetatable(L, "LuaExit.gc"); 152 | lua_pushstring(L, "__gc"); 153 | lua_pushcfunction(L, &LuaExit_exit); 154 | lua_settable(L, -3); 155 | // now add a metatable to the userdata 156 | lua_setmetatable(L, -2); // set it to the created userdata 157 | lua_setfield(L, LUA_REGISTRYINDEX, "LuaExit.userdata"); // anchor the userdata 158 | 159 | 160 | // First initialize the DSS client structure 161 | DSS_initialize(L, CancelFunc); // initialize and get API struct 162 | //TODO: Warning: statement below gets the utilID as static! can't use lib in more than 1 luastate 163 | DSSutilid = DSSapi->getutilid(L, DSS_LibID, NULL); 164 | 165 | luaL_register(L,"luaexit",LuaExit); 166 | #ifdef _DEBUG 167 | OutputDebugStringA("LuaExit: LuaOpen completed\n"); 168 | #endif 169 | return 1; 170 | }; 171 | 172 | -------------------------------------------------------------------------------- /luaexit/luaexit.h: -------------------------------------------------------------------------------- 1 | #ifndef luaexit_h 2 | #define luaexit_h 3 | 4 | 5 | // Macro to export the API 6 | #ifndef EXPORT_API 7 | #ifdef WIN32 8 | #define EXPORT_API __declspec(dllexport) 9 | #else 10 | #define EXPORT_API extern 11 | #endif 12 | #endif 13 | 14 | // add header implementation 15 | 16 | 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /luaexit/luaexit.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | 14 | {18D94275-AC41-41EB-8CEA-96ED6050002D} 15 | luaexit 16 | Win32Proj 17 | 18 | 19 | 20 | DynamicLibrary 21 | Unicode 22 | true 23 | 24 | 25 | DynamicLibrary 26 | Unicode 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | <_ProjectFileVersion>10.0.40219.1 40 | $(SolutionDir)$(Configuration)\ 41 | $(Configuration)\ 42 | true 43 | $(SolutionDir)$(Configuration)\ 44 | $(Configuration)\ 45 | false 46 | AllRules.ruleset 47 | 48 | 49 | AllRules.ruleset 50 | 51 | 52 | 53 | 54 | 55 | Disabled 56 | $(SolutionDir)darksidesync;C:\Users\Public\Lua\5.1\include;%(AdditionalIncludeDirectories) 57 | WIN32;_DEBUG;_WINDOWS;_USRDLL;LUA_TEMPLATE_EXPORTS;%(PreprocessorDefinitions) 58 | true 59 | EnableFastChecks 60 | MultiThreadedDebugDLL 61 | 62 | 63 | Level3 64 | EditAndContinue 65 | CompileAsC 66 | 67 | 68 | lua51.lib;%(AdditionalDependencies) 69 | C:\Users\Public\Lua\5.1\lib;%(AdditionalLibraryDirectories) 70 | true 71 | Windows 72 | MachineX86 73 | 74 | 75 | "$(ProjectDir)install.bat" 76 | 77 | 78 | 79 | 80 | MaxSpeed 81 | true 82 | WIN32;NDEBUG;_WINDOWS;_USRDLL;LUAEXIT_EXPORTS;%(PreprocessorDefinitions) 83 | MultiThreadedDLL 84 | true 85 | 86 | 87 | Level3 88 | ProgramDatabase 89 | $(SolutionDir)darksidesync;C:\Users\Public\Lua\5.1\include;%(AdditionalIncludeDirectories) 90 | 91 | 92 | true 93 | Windows 94 | true 95 | true 96 | MachineX86 97 | C:\Users\Public\Lua\5.1lfw\lib;%(AdditionalLibraryDirectories) 98 | lua51.lib;%(AdditionalDependencies) 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /luaexit/luaexit.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | 23 | 24 | Source Files 25 | 26 | 27 | Resource Files 28 | 29 | 30 | 31 | 32 | Header Files 33 | 34 | 35 | -------------------------------------------------------------------------------- /luaexit/luaexit_test.lua: -------------------------------------------------------------------------------- 1 | require("luarocks.require") 2 | 3 | local copas = require('copas.timer') -- load Copas socket scheduler 4 | local dss = require('dss') -- load darksidesync module 5 | 6 | -- the darksidesync lib has a Lua side piece of code that listens to the UDP signal whenever 7 | -- a background lib has delivered something. 8 | -- It provides a socket we need to listen on, and a callback that needs to be called when 9 | -- the socket is ready to read data. All we need to do is add them to our socket scheduler. 10 | -- We're using Copas as a socket scheduler, so add the darksidesync socket and the handler in a 11 | -- Copas way to the scheduler 12 | copas.addserver(dss.getsocket(), function(skt) 13 | skt = copas.wrap(skt) 14 | local hdlr = dss.gethandler() 15 | while true do 16 | hdlr(skt) 17 | end 18 | end) 19 | 20 | -- the darksidesync library has been loaded and is ready to go, now load our test 21 | -- background library, the signal handler. It only handles SIGTERM. 22 | local le = require('luaexit') 23 | 24 | -- initialize luaexit and provide it with a callback 25 | le.start(function(sig) 26 | print("Received signal; ", sig, ", now preparing for exit...") 27 | copas.exitloop() 28 | end) 29 | 30 | -- start the socket scheduler loop, so the socket gets read. 31 | print ("Starting the loop, send SIGTERM to test;") 32 | print (" kill -s SIGTERM ") 33 | print ("Use this pid;") 34 | os.execute("ps -A | grep lua") 35 | 36 | copas.loop() 37 | 38 | print ("Copas loop exited gracefully. Bye...") 39 | 40 | return 0 41 | 42 | -------------------------------------------------------------------------------- /luaexit/luasignal.geany: -------------------------------------------------------------------------------- 1 | 2 | [indentation] 3 | indent_width=4 4 | indent_type=1 5 | indent_hard_tab_width=8 6 | detect_indent=false 7 | indent_mode=2 8 | 9 | [project] 10 | name=DarkSideSync 11 | base_path=/home/thijs/CodeLite/DarkSideSync/ 12 | description= 13 | 14 | [long line marker] 15 | long_line_behaviour=1 16 | long_line_column=72 17 | 18 | [files] 19 | current_page=2 20 | FILE_NAME_0=57;C;0;16;1;1;0;/home/thijs/CodeLite/LuaSignal/src/luasignal.c;0;4 21 | FILE_NAME_1=114;Make;0;16;1;1;0;/home/thijs/CodeLite/LuaSignal/src/makefile;0;4 22 | FILE_NAME_2=769;Lua;0;16;1;1;0;/home/thijs/CodeLite/LuaSignal/test/testluasignal.lua;0;4 23 | FILE_NAME_3=263;C;0;16;1;1;0;/home/thijs/CodeLite/LuaSignal/src/darksidesync.h;0;4 24 | 25 | [build-menu] 26 | CFT_00_LB=_Compile 27 | CFT_00_CM=gcc -Wall -I/usr/include/lua5.1 -c "%f" 28 | CFT_00_WD= 29 | CFT_01_LB=_Build 30 | CFT_01_CM=gcc -Wall -I/usr/include/lua5.1 -o "%e" "%f" 31 | CFT_01_WD= 32 | filetypes=C; 33 | NF_00_LB=_Make 34 | NF_00_CM=make linux 35 | NF_00_WD= 36 | NF_01_LB=Make Custom _Target 37 | NF_01_CM=make 38 | NF_01_WD= 39 | -------------------------------------------------------------------------------- /luaexit/makefile: -------------------------------------------------------------------------------- 1 | PLAT?= none 2 | PLATS= macosx linux win32 3 | 4 | #------ 5 | # Hopefully no need to change anything below this line 6 | # 7 | all: $(PLAT) 8 | 9 | $(PLATS) none install local clean: 10 | @cd src; $(MAKE) $@ 11 | 12 | test: dummy 13 | lua test/hello.lua 14 | 15 | .PHONY: dummy 16 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | DarkSideSync 2 | ============ 3 | 4 | Usecase 5 | ------- 6 | I wanted to create a lua binding for a library. The library uses callbacks to report on its async operations. The main issue however was that the library created its own threadpool and the callbacks could be executed on any one of those. And that, sooner or later, will wreak havoc with Lua's single threaded nature. 7 | 8 | So while Lua's main thread is doing its Lua thing, it needs to be notified that there is some other thread that tries to deliver some result it needs to handle. 9 | 10 | To support this DarkSideSync (DSS) was created, DSS has no use on its own, it will only support other libraries in handling async callbacks. 11 | 12 | Main principle 13 | -------------- 14 | Whenever a background thread has some data to deliver it will call a DSS method to temporarily store it in a queue. Whenever Lua has time to poll for data from this queue, the background library will get a direct call from DSS to deliver its data to Lua. 15 | 16 | It supports async callbacks that just deliver data (the callback thread does not get blocked) and async callbacks that also expect a result (in this case the thread gets blocked until the Lua side thread has delivered the results) 17 | 18 | There is an optional notification using a UDP packet, which is an easy way to wake up Lua from a `select()` network method. 19 | 20 | Reference 21 | --------- 22 | Documentation can be found in the [repository and on the GitHub page](http://tieske.github.com/DarkSideSync), a [general description and some diagrams](http://tieske.github.com/DarkSideSync/background_worker_lib_spec_0v4.pdf) are also available. 23 | 24 | Positive 25 | -------- 26 | 27 | - It is very generic and cross platform. Because DSS takes care of threads, locks and (optionally) sockets, a library binding for an async library may require no platform specific code and still work cross-platform 28 | - It is setup as a separate library, loaded from Lua, no C links. It, sort of, dynamically extends the Lua C api 29 | - Supports async callbacks that only deliver data (the callback thread is not blocked) 30 | - Supports async callbacks that need a response (the callback thread is blocked until the Lua side response has been delivered) 31 | - Supports multiple async background libraries simultaneously 32 | - Supports multiple concurrent Lua states 33 | - It has been setup with the intend to support multiple versions of the DSS API, so in the future multiple background libraries can use a single DSS library, while talking to different versions of the API 34 | - The notification is also platform independent and even network library independent (eg. not bound to LuaSocket) any network library supporting UDP will do (this is the reason for not using file descriptors or pipes) 35 | 36 | Negative 37 | -------- 38 | 39 | - Notification using UDP packets requires some overhead, so for a very high number of callbacks it might be better to only use polling 40 | 41 | 42 | Copyright & License 43 | ------------------- 44 | Copyright 2012-2013, Thijs Schreijer. 45 | 46 | License is the same as Lua 5.1; [MIT license](http://opensource.org/licenses/MIT). 47 | 48 | The name 49 | -------- 50 | Lua = moon, dark side of the moon, externally managed threads that Lua cannot reach, you get it... 51 | 52 | Changes 53 | ------- 54 | 55 | 1.0 13-may-2013 56 | 57 | - Initial released version --------------------------------------------------------------------------------