Skip to content
This repository has been archived by the owner on Jul 4, 2020. It is now read-only.

Commit

Permalink
optimized text output processing for web interface (no more needless …
Browse files Browse the repository at this point in the history
…polls when no text is available)
  • Loading branch information
irmen committed Jul 9, 2017
1 parent 173adc2 commit db7170c
Show file tree
Hide file tree
Showing 9 changed files with 54 additions and 31 deletions.
1 change: 0 additions & 1 deletion ideas/ideas.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ Concepts for multiplayer MUD mode (and not really for single player I.F.):

General ideas/TODO:
-------------------
[html] in wsgi_handle_eventsource create an event in the io class to wait on new text rather than polling every 0.1 s
[code] Flesh out more items/templates in items.basic. Take ideas from objects in other mudlibs
[code] adding 'a' automatically sometimes gives strange results, "you take a someone's wallet"... "you take a some cash"...
[feature] combat system.
Expand Down
3 changes: 2 additions & 1 deletion tale/driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,8 @@ def start(self, game_file_or_path: str) -> None:
x._bind_target(self.zones)
self.unbound_exits = []
sys.excepthook = util.excepthook # install custom verbose crash reporter
self.start_main_loop() # doesn't exit!
self.start_main_loop() # doesn't exit! (unless game is killed)
self._stop_driver()

def start_main_loop(self):
raise NotImplementedError
Expand Down
1 change: 1 addition & 0 deletions tale/driver_if.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ def start_main_loop(self):
driver_thread.start()
connection.singleplayer_mainloop() # this doesn't return! (unless you CTRL-C it)
self._stop_mainloop = True
connection.destroy()

def show_motd(self, player: Player, notify_no_motd: bool=False) -> None:
pass # no motd in IF mode
Expand Down
18 changes: 10 additions & 8 deletions tale/player.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import queue
import time
from threading import Event
from typing import Any, Sequence, Tuple, IO, Dict, Set, List, Union
from typing import Any, Sequence, Tuple, IO, Optional, Set, List, Union

from . import base
from . import hints
Expand Down Expand Up @@ -336,16 +336,18 @@ def __init__(self, player: Player=None, io: IoAdapterBase=None) -> None:
self.io = io
self.need_new_input_prompt = True

def get_output(self) -> str:
def get_output(self) -> Optional[str]:
"""
Gets the accumulated output lines, formats them nicely, and clears the buffer.
If there is nothing to be outputted, None is returned.
"""
formatted = self.io.render_output(self.player._output.get_paragraphs(),
width=self.player.screen_width, indent=self.player.screen_indent)
if formatted and self.player.transcript:
self.player.transcript.write(formatted)
return formatted or None
paragraphs = self.player._output.get_paragraphs()
if paragraphs:
formatted = self.io.render_output(paragraphs, width=self.player.screen_width, indent=self.player.screen_indent)
if formatted and self.player.transcript:
self.player.transcript.write(formatted)
return formatted or None
return None

@property
def last_output_line(self) -> str:
Expand Down Expand Up @@ -412,7 +414,7 @@ def critical_error(self) -> None:
self.io.critical_error()

def singleplayer_mainloop(self) -> None:
self.io.singleplayer_mainloop(self) # this does not return
self.io.singleplayer_mainloop(self) # this does not return, unless game is closed

def pause(self, unpause: bool=False) -> None:
self.io.pause(unpause)
Expand Down
2 changes: 1 addition & 1 deletion tale/tio/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@

DEFAULT_SCREEN_WIDTH = 72
DEFAULT_SCREEN_INDENT = 2
DEFAULT_SCREEN_DELAY = 0 # XXX 40
DEFAULT_SCREEN_DELAY = 40
4 changes: 2 additions & 2 deletions tale/tio/console_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import signal
import sys
import threading
from typing import Iterable, Tuple, Any, Optional, List
from typing import Sequence, Tuple, Any, Optional, List
try:
import prompt_toolkit
from prompt_toolkit.contrib.completers import WordCompleter
Expand Down Expand Up @@ -146,7 +146,7 @@ def abort_all_input(self, player: Player) -> None:
sys.stderr.flush()
os.kill(os.getpid(), signal.SIGINT)

def render_output(self, paragraphs: Iterable[Tuple[str, bool]], **params: Any) -> Optional[str]:
def render_output(self, paragraphs: Sequence[Tuple[str, bool]], **params: Any) -> Optional[str]:
"""
Render (format) the given paragraphs to a text representation.
It doesn't output anything to the screen yet; it just returns the text string.
Expand Down
50 changes: 35 additions & 15 deletions tale/tio/if_browser_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
from email.utils import formatdate, parsedate
from hashlib import md5
from html import escape as html_escape
from threading import Lock
from typing import Iterable, Tuple, Any, Optional, Dict, Callable, List
from threading import Lock, Event
from typing import Iterable, Sequence, Tuple, Any, Optional, Dict, Callable, List
from urllib.parse import parse_qs
from wsgiref.simple_server import make_server, WSGIRequestHandler, WSGIServer

Expand Down Expand Up @@ -65,14 +65,20 @@ def __init__(self, player_connection: PlayerConnection, wsgi_server: WSGIServer)
self.__html_to_browser = [] # type: List[str] # the lines that need to be displayed in the player's browser
self.__html_special = [] # type: List[str] # special out of band commands (such as 'clear')
self.__html_to_browser_lock = Lock()
self.__new_html_available = Event()

def destroy(self) -> None:
self.__new_html_available.set()

def append_html_to_browser(self, text: str) -> None:
with self.__html_to_browser_lock:
self.__html_to_browser.append(text)
self.__new_html_available.set()

def append_html_special(self, text: str) -> None:
with self.__html_to_browser_lock:
self.__html_special.append(text)
self.__new_html_available.set()

def get_html_to_browser(self) -> List[str]:
with self.__html_to_browser_lock:
Expand All @@ -84,6 +90,10 @@ def get_html_special(self) -> List[str]:
special, self.__html_special = self.__html_special, []
return special

def wait_html_available(self):
self.__new_html_available.wait()
self.__new_html_available.clear()

def singleplayer_mainloop(self, player_connection: PlayerConnection) -> None:
"""mainloop for the web browser interface for single player mode"""
import webbrowser
Expand Down Expand Up @@ -112,28 +122,35 @@ def pause(self, unpause: bool=False) -> None:
def clear_screen(self) -> None:
self.append_html_special("clear")

def render_output(self, paragraphs: Iterable[Tuple[str, bool]], **params: Any) -> Optional[str]:
for text, formatted in paragraphs:
text = self.convert_to_html(text)
if text == "\n":
text = "<br>"
if formatted:
self.__html_to_browser.append("<p>" + text + "</p>\n")
else:
self.__html_to_browser.append("<pre>" + text + "</pre>\n")
def render_output(self, paragraphs: Sequence[Tuple[str, bool]], **params: Any) -> Optional[str]:
if not paragraphs:
return None
with self.__html_to_browser_lock:
for text, formatted in paragraphs:
text = self.convert_to_html(text)
if text == "\n":
text = "<br>"
if formatted:
self.__html_to_browser.append("<p>" + text + "</p>\n")
else:
self.__html_to_browser.append("<pre>" + text + "</pre>\n")
self.__new_html_available.set()
return None # the output is pushed to the browser via a buffer, rather than printed to a screen

def output(self, *lines: str) -> None:
super().output(*lines)
for line in lines:
self.output_no_newline(line)
with self.__html_to_browser_lock:
for line in lines:
self.output_no_newline(line)
self.__new_html_available.set()

def output_no_newline(self, text: str) -> None:
super().output_no_newline(text)
text = self.convert_to_html(text)
if text == "\n":
text = "<br>"
self.__html_to_browser.append("<p>" + text + "</p>\n")
self.__new_html_available.set()

def convert_to_html(self, line: str) -> str:
"""Convert style tags to html"""
Expand Down Expand Up @@ -304,7 +321,11 @@ def wsgi_handle_eventsource(self, environ: Dict[str, Any], parameters: Dict[str,
return self.wsgi_internal_server_error_json(start_response, "not logged in")
start_response('200 OK', [('Content-Type', 'text/event-stream; charset=utf-8'),
('Cache-Control', 'no-cache')])
while self.driver.is_running() and conn.io and conn.player:
while self.driver.is_running():
if conn.io and conn.player:
conn.io.wait_html_available()
if not conn.io or not conn.player:
break
html = conn.io.get_html_to_browser()
special = conn.io.get_html_special()
if html or special:
Expand All @@ -317,7 +338,6 @@ def wsgi_handle_eventsource(self, environ: Dict[str, Any], parameters: Dict[str,
result = "event: text\nid: {event_id}\ndata: {data}\n\n"\
.format(event_id=str(time.time()), data=json.dumps(response))
yield result.encode("utf-8")
time.sleep(0.1) # @todo add event on conn.io so we don't have to poll it for new text

def wsgi_handle_tabcomplete(self, environ: Dict[str, Any], parameters: Dict[str, str],
start_response: WsgiStartResponseType) -> Iterable[bytes]:
Expand Down
2 changes: 1 addition & 1 deletion tale/tio/iobase.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def abort_all_input(self, player) -> None:
"""abort any blocking input, if at all possible"""
pass

def render_output(self, paragraphs: Iterable[Tuple[str, bool]], **params: Any) -> Optional[str]:
def render_output(self, paragraphs: Sequence[Tuple[str, bool]], **params: Any) -> Optional[str]:
"""
Render (format) the given paragraphs to a text representation.
It doesn't output anything to the screen yet; it just returns the text string.
Expand Down
4 changes: 2 additions & 2 deletions tale/tio/tkinter_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import tkinter
import tkinter.font
import tkinter.messagebox
from typing import Iterable, Tuple, Any, Optional
from typing import Sequence, Tuple, Any, Optional

from . import iobase
from .. import vfs
Expand Down Expand Up @@ -64,7 +64,7 @@ def abort_all_input(self, player) -> None:
"""abort any blocking input, if at all possible"""
player.store_input_line("")

def render_output(self, paragraphs: Iterable[Tuple[str, bool]], **params: Any) -> Optional[str]:
def render_output(self, paragraphs: Sequence[Tuple[str, bool]], **params: Any) -> Optional[str]:
"""
Render (format) the given paragraphs to a text representation.
It doesn't output anything to the screen yet; it just returns the text string.
Expand Down

0 comments on commit db7170c

Please sign in to comment.