Search code examples
javascriptpythonwebsockettwisted

Why server is updating clients every 1 second instead of 100ms - full sourcecode available


My server is set to tick and update all clients every 100ms - see self.TICKS_PER_SECOND = 10, 1000/10 = 100ms.

However, the "clock" in the html only gets update every 1 second. Why?

If I keep clicking on the submit button than the clock gets updated faster. Why?

app.py

import logging
import sys
from datetime import datetime
import time
current_milli_time = lambda: int(round(time.time() * 1000))

import json
import threading

from twisted.web.static import File
from twisted.python import log
from twisted.web.server import Site
from twisted.web.resource import Resource

from twisted.internet import pollreactor
pollreactor.install()

from twisted.internet import reactor

from autobahn.twisted.websocket import WebSocketServerFactory, \
    WebSocketServerProtocol

from autobahn.twisted.resource import WebSocketResource


class AppGameServerProtocol(WebSocketServerProtocol):

    def onOpen(self):
        """
        """
        self.factory.register(self)
        self.factory.onConnected(self)

    def onConnect(self, request):
        print("Client connecting: {}".format(request.peer))

    def connectionLost(self, reason):
        self.factory.unregister(self)

    def onMessage(self, payload, isBinary):
        self.factory.communicate(self, payload, isBinary)


class AppGameFactory(WebSocketServerFactory):

    def __init__(self, *args, **kwargs):
        super(AppGameFactory, self).__init__(*args, **kwargs)
        self.clients = {}

    def register(self, client):
        self.clients[client.peer] = {"object": client, "partner": None}

    def unregister(self, client):
        self.clients.pop(client.peer)


    def onConnected(self,client):
        client.sendMessage(json.dumps({"type": "connect"}))

    def communicate(self, client, payload, isBinary):
        print "msg received", payload

    def sendMessageAll(self, message):
        for i in self.clients:
            c = self.clients[i]
            c["object"].sendMessage(message)

class SummingThread(threading.Thread):
    def __init__(self, fac):
        super(SummingThread, self).__init__()
        self.fac = fac

        self.TICKS_PER_SECOND = 10
        self.SKIP_TICKS = 1000 / self.TICKS_PER_SECOND
        self.MAX_FRAMESKIP = 5
        self.loops = 0
        self.next_game_tick = current_milli_time()

    def run(self):
        while True:
            self.loops = 0
            while current_milli_time() > self.next_game_tick and self.loops < self.MAX_FRAMESKIP:

                self.fac.sendMessageAll("clock: " + datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3])
                self.next_game_tick += self.SKIP_TICKS
                self.loops += 1



if __name__ == "__main__":
    log.startLogging(sys.stdout)

    factory = AppGameFactory(u"ws://127.0.0.1:8080")
    factory.protocol = AppGameServerProtocol
    resource = WebSocketResource(factory)

    root = Resource()
    root.putChild('', File('index.html'))

    root.putChild(u"ws", resource)

    thread1 = SummingThread(factory)
    thread1.start()

    try:
        site = Site(root)
        reactor.listenTCP(8080, site)
        reactor.run()
    except Exception as e:
        logging.exception("message")

index.html

<!DOCTYPE html>
<html>
<head>
    <script type="text/javascript">
        window.addEventListener("load", function() {

            var msgReceived = 0

            // create websocket instance
            var mySocket = new WebSocket("ws://localhost:8080/ws");

            // add event listener reacting when message is received
            mySocket.onmessage = function (event) {
                var output = document.getElementById("output");
                // put text into our output div
                msgReceived++;
                output.textContent = event.data + " msgReceived: " + msgReceived;
                //console.log(event.data);
            };

            var form = document.getElementsByClassName("foo");
            var input = document.getElementById("input");

            form[0].addEventListener("submit", function (e) {
                // on forms submission send input to our server
                input_text = input.value;
                mySocket.send(input_text);
                e.preventDefault()
            })
        });

    </script>
<style>
    /* just some super ugly css to make things bit more readable*/
    div {
        margin: 2em;
    }
    form {
        margin: 2em;
    }
</style>
</head>
<body>
    <form class="foo">
        <input id="input"></input>
        <input type="submit"></input>
    </form>
    <div id="output"></div>
</body>
</html>

Solution

  • You're calling Twisted APIs from multiple threads. This is not allowed. You may only call Twisted APIs from the reactor thread - except for a couple specific thread-safe APIs. Mostly reactor.callFromThread which schedules a function to run in the reactor thread.

    Consider replacing SummingThread with a construction based on twisted.internet.task.LoopingCall.

    For example:

    from twisted.internet.task import LoopingCall
    
    def game_tick(factory, skip_count):
        now = datetime.utcnow()
        factory.sendMessageAll(
            "clock: " + now.strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
        )
    
    
    factory = # ...
    task = LoopingCall.withCount(partial(game_tick, factory))
    task.start(0.1)
    # ...
    reactor.run()
    

    game_tick will be called ten times per second (or, if less frequently because load is too high or some other problem, skip_count will be set greater than one to indicate how many ticks were missed).