Prechádzať zdrojové kódy

Lua timer: Allow to use functions (closures) and set intervals

bel2125 4 rokov pred
rodič
commit
5672c04936
3 zmenil súbory, kde vykonal 371 pridanie a 40 odobranie
  1. 149 40
      src/mod_lua.inl
  2. 105 0
      test/websocket2.lua
  3. 117 0
      test/websocket2.xhtml

+ 149 - 40
src/mod_lua.inl

@@ -1987,51 +1987,56 @@ lwebsock_write(lua_State *L)
 }
 
 
-struct laction_arg {
-	lua_State *state;
+struct laction_string_arg {
+	lua_State *L;
 	const char *script;
 	pthread_mutex_t *pmutex;
 	char txt[1];
 };
 
+struct laction_funcref_arg {
+	lua_State *L;
+	const char *script;
+	pthread_mutex_t *pmutex;
+	int funcref;
+};
+
 
 static int
-lua_action(struct laction_arg *arg)
+lua_action_string(struct laction_string_arg *arg)
 {
 	int err, ok;
 	struct mg_context *ctx;
 
 	(void)pthread_mutex_lock(arg->pmutex);
 
-	lua_pushlightuserdata(arg->state, (void *)&lua_regkey_ctx);
-	lua_gettable(arg->state, LUA_REGISTRYINDEX);
-	ctx = (struct mg_context *)lua_touserdata(arg->state, -1);
-	lua_pop(arg->state, 1);
+	lua_pushlightuserdata(arg->L, (void *)&lua_regkey_ctx);
+	lua_gettable(arg->L, LUA_REGISTRYINDEX);
+	ctx = (struct mg_context *)lua_touserdata(arg->L, -1);
+	lua_pop(arg->L, 1);
 
-	err = luaL_loadstring(arg->state, arg->txt);
+	err = luaL_loadstring(arg->L, arg->txt);
 	if (err != 0) {
 		struct mg_connection fc;
-		lua_cry(
-		    fake_connection(&fc, ctx), err, arg->state, arg->script, "timer");
+		lua_cry(fake_connection(&fc, ctx), err, arg->L, arg->script, "timer");
 		(void)pthread_mutex_unlock(arg->pmutex);
 		return 0;
 	}
-	err = lua_pcall(arg->state, 0, 1, 0);
+	err = lua_pcall(arg->L, 0, 1, 0);
 	if (err != 0) {
 		struct mg_connection fc;
-		lua_cry(
-		    fake_connection(&fc, ctx), err, arg->state, arg->script, "timer");
+		lua_cry(fake_connection(&fc, ctx), err, arg->L, arg->script, "timer");
 		(void)pthread_mutex_unlock(arg->pmutex);
 		return 0;
 	}
 
-	ok = lua_type(arg->state, -1);
-	if (lua_isboolean(arg->state, -1)) {
-		ok = lua_toboolean(arg->state, -1);
+	ok = lua_type(arg->L, -1);
+	if (lua_isboolean(arg->L, -1)) {
+		ok = lua_toboolean(arg->L, -1);
 	} else {
 		ok = 0;
 	}
-	lua_pop(arg->state, 1);
+	lua_pop(arg->L, 1);
 
 	(void)pthread_mutex_unlock(arg->pmutex);
 
@@ -2039,9 +2044,53 @@ lua_action(struct laction_arg *arg)
 }
 
 
+static int
+lua_action_funcref(struct laction_funcref_arg *arg)
+{
+	int err, ok;
+	struct mg_context *ctx;
+
+	(void)pthread_mutex_lock(arg->pmutex);
+
+	lua_pushlightuserdata(arg->L, (void *)&lua_regkey_ctx);
+	lua_gettable(arg->L, LUA_REGISTRYINDEX);
+	ctx = (struct mg_context *)lua_touserdata(arg->L, -1);
+	lua_pop(arg->L, 1);
+
+	lua_rawgeti(arg->L, LUA_REGISTRYINDEX, arg->funcref);
+	err = lua_pcall(arg->L, 0, 1, 0);
+	if (err != 0) {
+		struct mg_connection fc;
+		lua_cry(fake_connection(&fc, ctx), err, arg->L, arg->script, "timer");
+		(void)pthread_mutex_unlock(arg->pmutex);
+		return 0;
+	}
+
+	ok = lua_type(arg->L, -1);
+	if (lua_isboolean(arg->L, -1)) {
+		ok = lua_toboolean(arg->L, -1);
+	} else {
+		ok = 0;
+	}
+	lua_pop(arg->L, 1);
+
+	(void)pthread_mutex_unlock(arg->pmutex);
+
+	return ok;
+}
+
+
+static void
+lua_action_string_cancel(struct laction_string_arg *arg)
+{
+	mg_free(arg);
+}
+
+
 static void
-lua_action_cancel(struct laction_arg *arg)
+lua_action_funcref_cancel(struct laction_funcref_arg *arg)
 {
+	luaL_unref(arg->L, LUA_REGISTRYINDEX, arg->funcref);
 	mg_free(arg);
 }
 
@@ -2052,12 +2101,9 @@ lwebsocket_set_timer(lua_State *L, int is_periodic)
 #if defined(USE_TIMERS) && defined(USE_WEBSOCKET)
 	int num_args = lua_gettop(L);
 	struct lua_websock_data *ws;
-	int type1, type2, ok = 0;
-	double timediff;
+	int type1, type2, type3, ok = 0;
+	double delay, interval;
 	struct mg_context *ctx;
-	struct laction_arg *arg;
-	const char *action_txt;
-	size_t action_txt_len;
 
 	lua_pushlightuserdata(L, (void *)&lua_regkey_ctx);
 	lua_gettable(L, LUA_REGISTRYINDEX);
@@ -2074,22 +2120,59 @@ lwebsocket_set_timer(lua_State *L, int is_periodic)
 
 	type1 = lua_type(L, 1);
 	type2 = lua_type(L, 2);
+	type3 = lua_type(L, 3);
+
+	/* Must have at least two arguments, ant the first one has to be some text
+	 */
+	if ((num_args < 2) || (num_args > 3)) {
+		return luaL_error(L, "invalid arguments for set_timer/interval() call");
+	}
+
+	/* Second argument is the delay (and interval) */
+	if (type2 != LUA_TNUMBER) {
+		return luaL_error(L, "invalid arguments for set_timer/interval() call");
+	}
+	delay = (double)lua_tonumber(L, 2);
+	interval = (is_periodic ? delay : 0.0);
+
+	/* Third argument (optional) could be an interval */
+	if (num_args > 2) {
+		if (is_periodic || (type3 != LUA_TNUMBER)) {
+			return luaL_error(
+			    L, "invalid arguments for set_timer/interval() call");
+		}
+		interval = (double)lua_tonumber(L, 3);
+	}
+
+	/* Check numbers */
+	if ((delay < 0.0) || (interval < 0.0)) {
+		return luaL_error(L, "invalid arguments for set_timer/interval() call");
+	}
+
+	/* First value specifies the action */
+	if (type1 == LUA_TSTRING) {
+
+		/* Action could be passed as a string value */
+		struct laction_string_arg *arg;
+		const char *action_txt;
+		size_t action_txt_len;
 
-	if ((num_args == 2) && (type1 == LUA_TSTRING) && (type2 == LUA_TNUMBER)) {
-		/* called with 2 args: action (string), time (number) */
 		action_txt = lua_tostring(L, 1);
+		if ((action_txt == NULL) || (action_txt[0] == 0)) {
+			return luaL_error(
+			    L, "invalid arguments for set_timer/interval() call");
+		}
 		action_txt_len = strlen(action_txt);
-		timediff = (double)lua_tonumber(L, 2);
 
-		arg = (struct laction_arg *)mg_malloc_ctx(sizeof(struct laction_arg)
-		                                              + action_txt_len + 10,
-		                                          ctx);
+		/* Create timer data structure and schedule timer */
+		arg = (struct laction_string_arg *)mg_malloc_ctx(
+		    sizeof(struct laction_string_arg) + action_txt_len + 10, ctx);
 		if (!arg) {
 			return luaL_error(L, "out of memory");
 		}
 
 		/* Argument for timer */
-		arg->state = L;
+		arg->L = L;
 		arg->script = ws->script;
 		arg->pmutex = &(ws->ws_mutex);
 		memcpy(arg->txt, "return(", 7);
@@ -2098,21 +2181,47 @@ lwebsocket_set_timer(lua_State *L, int is_periodic)
 		arg->txt[action_txt_len + 8] = 0;
 		if (0
 		    == timer_add(ctx,
-		                 timediff,
-		                 is_periodic,
+		                 delay,
+		                 interval,
 		                 1,
-		                 lua_action,
+		                 (taction)lua_action_string,
 		                 (void *)arg,
-		                 lua_action_cancel)) {
+		                 (tcancelaction)lua_action_string_cancel)) {
 			/* Timer added successfully */
 			ok = 1;
 		}
+	} else if (type1 == LUA_TFUNCTION) {
 
-	} else if ((num_args == 2) && (type1 == LUA_TFUNCTION)
-	           && (type2 == LUA_TNUMBER)) {
-		/* called with 2 args: action (function), time (number) */
-		/* TODO (mid): not implemented yet */
-		return luaL_error(L, "invalid arguments for set_timer/interval() call");
+		/* Action could be passed as a function */
+		int funcref;
+		struct laction_funcref_arg *arg;
+
+		lua_pushvalue(L, 1);
+		funcref = luaL_ref(L, LUA_REGISTRYINDEX);
+
+		/* Create timer data structure and schedule timer */
+		arg = (struct laction_funcref_arg *)
+		    mg_malloc_ctx(sizeof(struct laction_funcref_arg), ctx);
+		if (!arg) {
+			return luaL_error(L, "out of memory");
+		}
+
+		/* Argument for timer */
+		arg->L = L;
+		arg->script = ws->script;
+		arg->pmutex = &(ws->ws_mutex);
+		arg->funcref = funcref;
+		if (0
+		    == timer_add(ctx,
+		                 delay,
+		                 interval,
+		                 1,
+		                 (taction)lua_action_funcref,
+		                 (void *)arg,
+		                 (tcancelaction)lua_action_funcref_cancel)) {
+			/* Timer added successfully */
+			ok = 1;
+		}
 	} else {
 		return luaL_error(L, "invalid arguments for set_timer/interval() call");
 	}
@@ -2387,7 +2496,7 @@ static void
 reg_gc(lua_State *L, void *conn)
 {
 	/* Key element */
-	lua_pushlightuserdata(L, &lua_regkey_dtor);
+	lua_pushlightuserdata(L, (void *)&lua_regkey_dtor);
 
 	/* Value element */
 	lua_newuserdata(L, 0);

+ 105 - 0
test/websocket2.lua

@@ -0,0 +1,105 @@
+
+function trace(text)
+    local f = io.open("websocket2.trace", "a")
+    f:write(os.date() .. " - " .. text .. "\n")
+    f:close()
+end
+
+function iswebsocket()
+  return mg.lua_type == "websocket"
+end
+
+trace("called with Lua type " .. tostring(mg.lua_type))
+
+if not iswebsocket() then
+  trace("no websocket")
+  mg.write("HTTP/1.0 403 Forbidden\r\n")
+  mg.write("Connection: close\r\n")
+  mg.write("\r\n")
+  mg.write("forbidden")
+  return
+end
+
+
+-- Serialize table to string
+function ser(val)
+  local t
+  if type(val) == "table" then
+    for k,v in pairs(val) do
+      if t then
+        t = t .. ", " .. ser(k) .. "=" .. ser(v)
+      else
+        t = "{" .. ser(k) .. "=" .. ser(v)
+      end
+    end
+    t = t .. "}"
+  else
+    t = tostring(val)
+  end
+  return t
+end
+
+-- table of all active connection
+allConnections = {}
+
+-- function to get a client identification string
+function who(tab)
+  local ri = allConnections[tab.client].request_info
+  return ri.remote_addr .. ":" .. ri.remote_port
+end
+
+-- Callback to accept or reject a connection
+function open(tab)
+  allConnections[tab.client] = tab
+  trace("open[" .. who(tab) .. "]: " .. ser(tab))
+  return true -- return true to accept the connection
+end
+
+-- Callback for "Websocket ready"
+function ready(tab)
+  trace("ready[" .. who(tab) .. "]: " .. ser(tab))
+  mg.write(tab.client, "text", "Websocket ready")
+  mg.write(tab.client, 1, "-->h 180");
+  mg.write(tab.client, "-->m 180");
+  senddata()
+  mg.set_interval(timer, 1)
+  return true -- return true to keep the connection open
+end
+
+-- Callback for "Websocket received data"
+function data(tab)
+    trace("data[" .. who(tab) .. "]: " .. ser(tab))
+    senddata()
+    return true -- return true to keep the connection open
+end
+
+-- Callback for "Websocket is closing"
+function close(tab)
+    trace("close[" .. who(tab) .. "]: " .. ser(tab))
+    mg.write("text", "end")
+    allConnections[tab.client] = nil
+end
+
+function senddata()
+    local date = os.date('*t');
+    local hand = (date.hour%12)*60+date.min;
+
+    mg.write("text", string.format("%u:%02u:%02u", date.hour, date.min, date.sec));
+
+    if (hand ~= lasthand) then
+        mg.write(1, string.format("-->h %f", hand*360/(12*60)));
+        mg.write(   string.format("-->m %f", date.min*360/60));
+        lasthand = hand;
+    end
+
+    if bits and content then
+        data(bits, content)
+    end
+end
+
+function timer()
+    trace("timer")
+    senddata()
+    return true -- return true to keep an interval timer running
+end
+

+ 117 - 0
test/websocket2.xhtml

@@ -0,0 +1,117 @@
+<!DOCTYPE HTML>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+  <meta charset="UTF-8"></meta>
+  <title>Websocket test</title>
+  <style type="text/css" media="screen">
+    body { background:#eee; margin:0 }
+    .main {
+      display:block; border:1px solid #ccc; position:absolute;
+      top:5%; left:5%; width:90%; height:90%; background:#fff;
+    }
+  </style>
+</head>
+<body>
+  <script type="text/javascript"><![CDATA[
+
+    var connection;
+    var websock_text_field;
+    var hand_hour;
+    var hand_min;
+
+    function queryStringElem(name, idx) {
+      if (typeof(queryStringElem_Table) != "object") {
+        queryStringElem_Table = {};
+        window.location.search.slice(1).split('&').forEach(
+          function(keyValuePair) {
+            keyValuePair = keyValuePair.split('=');
+            if (typeof(queryStringElem_Table[keyValuePair[0]]) != "object") {
+              queryStringElem_Table[keyValuePair[0]] = [];
+            }
+            var idx = queryStringElem_Table[keyValuePair[0]].length+1;
+            queryStringElem_Table[keyValuePair[0]][idx] = keyValuePair[1] || '';
+          }
+        );
+      }
+      idx = idx || 1;
+      if (queryStringElem_Table[name]) {
+        return queryStringElem_Table[name][idx];
+      }
+      return null;
+    }
+
+    function webSockKeepAlive() {
+      if (keepAlive) {
+        connection.send('client still alive');
+        console.log('send keep alive')
+        setTimeout("webSockKeepAlive()", 10000);
+      }
+    }
+
+    function load() {
+      var wsproto = (location.protocol === 'https:') ? "wss:" : "ws:";
+      connection = new WebSocket(wsproto + "//" + window.location.host + "/websocket2.lua");
+      websock_text_field = document.getElementById('websock_text_field');
+      hand_min = document.getElementById('hand_min');
+      hand_hour = document.getElementById('hand_hour');
+
+      var ka = queryStringElem("keepAlive");
+      if (ka) {
+        ka = ka.toLowerCase();
+        use_keepAlive = (ka!="false") && (ka!="f") && (ka!="no") && (ka!="n") && (ka!=0);
+      } else {
+        use_keepAlive = true;
+      }
+
+      connection.onopen = function () {
+        keepAlive = use_keepAlive;
+        webSockKeepAlive();
+      };
+
+      // Log errors
+      connection.onerror = function (error) {
+        keepAlive = false;
+        alert("WebSocket error");
+        connection.close();
+      };
+
+      // Log messages from the server
+      connection.onmessage = function (e) {
+        var lCmd = e.data.substring(0,3);
+        if (lCmd == "-->") {
+          console.log(e.data);
+          var lDirection = Number(e.data.substring(5));
+          if (e.data[3] == 'h') {
+            hand_hour.setAttribute("transform", "rotate(" + lDirection + " 800 600)");
+          }
+          if (e.data[3] == 'm') {
+            hand_min.setAttribute("transform", "rotate(" + lDirection + " 800 600)");
+          }
+        } else {
+          websock_text_field.textContent = e.data;
+        }
+      };
+
+      console.log("load");
+    }
+
+  ]]></script>
+
+<svg class="main"
+  xmlns="http://www.w3.org/2000/svg"
+  xmlns:svg="http://www.w3.org/2000/svg"
+  version="1.1"
+  xmlns:xlink="http://www.w3.org/1999/xlink"
+  viewBox="0 0 1600 1200" preserveAspectRatio="xMinYMin meet"
+  onload="load()"
+  >
+
+  <circle id="line_a" cx="800" cy="600" r="500" style="stroke:rgb(255,0,0); stroke-width:5; fill:rgb(200,200,200)"/>
+  <polygon points="800,200 900,300 850,300 850,600 750,600 750,300 700,300" style="fill:rgb(100,0,0)" transform="rotate(0,800,600)" id="hand_hour"/>
+  <polygon points="800,100 840,200 820,200 820,600 780,600 780,200 760,200" style="fill:rgb(0,100,0)" transform="rotate(0,800,600)" id="hand_min"/>
+  <text id="websock_text_field" x="800" y="600" text-anchor="middle" font-size="50px" fill="red">No websocket connection yet</text>
+
+</svg>
+
+</body>
+</html>