Explorar el Código

Add simulator loop example

master
fenceFoil hace 4 años
padre
commit
699b13cdd2
Se han modificado 5 ficheros con 180 adiciones y 2 borrados
  1. +2
    -1
      .gitignore
  2. +2
    -0
      src/envVars.bat
  3. +3
    -1
      src/requirements.txt
  4. +3
    -0
      src/simulatorSampleApp.bat
  5. +170
    -0
      src/simulatorSampleApp.py

+ 2
- 1
.gitignore Ver fichero

@@ -1,2 +1,3 @@
__pycache__
venv
venv
logs

+ 2
- 0
src/envVars.bat Ver fichero

@@ -0,0 +1,2 @@
set GOTIFY_ADDRESS=http://billkarnavas.com:8080
set GOTIFY_APIKEY=AXoe0zpZN6iO7wv

+ 3
- 1
src/requirements.txt Ver fichero

@@ -6,4 +6,6 @@ fastapi
aiofiles
python-multipart
uvicorn[standard]
jinja2
jinja2
readerwriterlock
requests

+ 3
- 0
src/simulatorSampleApp.bat Ver fichero

@@ -0,0 +1,3 @@
call ../venv/scripts/Activate.bat
call envVars.bat
uvicorn simulatorSampleApp:app --reload --no-access-log --log-level warning --port 5777 --host 0.0.0.0

+ 170
- 0
src/simulatorSampleApp.py Ver fichero

@@ -0,0 +1,170 @@
#standard
import asyncio
import html
import traceback
from datetime import datetime
import logging
from logging import debug, info, warning, error, critical, exception
import os
import threading
import time
import uuid
#custom
import dataset
from readerwriterlock import rwlock
import requests
from fastapi import FastAPI, WebSocket, Form, File, UploadFile, Response, Request, WebSocketDisconnect, websockets
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
import starlette.status as status
from starlette.responses import RedirectResponse

# ---- LOGGER SETUP ----

LOGS_PATH = '../logs/'
def setupLogger(level = logging.INFO):

startTime = datetime.now().strftime("%Y-%m-%d %H-%M-%S")

logFormat = "[%(levelname)s] %(asctime)s: %(message)s"
logFormatter = logging.Formatter(logFormat)

# Log to a file and to the error stream
if not os.path.exists(LOGS_PATH):
os.makedirs(LOGS_PATH)
logging.basicConfig(filename='{}log {}.txt'.format(LOGS_PATH, startTime),
level=level, format=logFormat)
streamHandler = logging.StreamHandler()
streamHandler.setLevel(level)
streamHandler.setFormatter(logFormatter)
logging.getLogger().addHandler(streamHandler)

info("New instance started")

# ---- END LOGGER SETUP ---

def sendGotifyNotification(title, text, priority):
def doSendGotifyNotification(title, text, priority):
try:
response = requests.post(os.environ['GOTIFY_ADDRESS']+'/message', params={
"token": os.environ['GOTIFY_APIKEY']}, json={'message': text, 'title': title, 'priority': priority})
if response.status_code != 200:
error("sendGotifyNotification(): server error {}, description: {}".format(
response.status_code, response.text))
except requests.exceptions.ConnectionError:
error("sendGotifyNotification(): connection error. not retrying.")
t = threading.Thread(target=doSendGotifyNotification,
args=(title, text, priority))
t.start()

def make_readable_sim_timestamp(ms):
days = int(ms // (24*60*60*1000))
hours = int(ms // (60*60*1000))
hours -= days*24
mins = int(ms // (60*1000))
mins -= hours*60
secs = int(ms // 1000)
secs -= mins*60
msecs = int(ms % 1000)
return f"{days}d{hours}h{mins}m{secs}.{msecs}s"


app = FastAPI()
app.mount('/static', StaticFiles(directory="./static"), name="static")
templates = Jinja2Templates(directory="templates")
@app.get('/', response_class=HTMLResponse)
async def getRoot(request: Request):
return RedirectResponse('/static/index.html', status_code=status.HTTP_302_FOUND)

db = dataset.connect('sqlite:///../state.db')

# Not accurate, but roughly in hz.
# Does not affect speed of simulation, only how often it is sampled.
simRefreshRate = 50

# ---- Create the data used to interface between websockets and the simulator ----
# Keep a list of incoming events from outside world
incomingEventQueue = asyncio.Queue(maxsize=10000)
# Keep a list of events generated by the sim
outgoingEventQueue = asyncio.Queue(maxsize=10000)

# Store connections and their subscription information
conns_locks = rwlock.RWLockFair()
conns = {}

# Create a world
world_locks = rwlock.RWLockFair()
simWorld = None

simShutdownSignal = False
simWasShutdownSignal = False
# ---- End interface data setup ----

async def simulator_runner():
global simShutdownSignal, simWasShutdownSignal
runnerStartTimeMS = round((time.monotonic_ns()/1_000_000))
while not simShutdownSignal:
# Pad cycle
await asyncio.sleep(1.0/simRefreshRate)

# Simulate
print('hi')

with world_locks.gen_wlock():
# Accept new events
# Run simulation tick
pass
simWasShutdownSignal = True

@app.on_event("startup")
async def setupSimulator():
setupLogger(logging.DEBUG)
asyncio.create_task(simulator_runner())

@app.websocket('/websocket')
async def runGameSession(websocket: WebSocket):

async def sendDisplay(dest, rawHTML):
await websocket.send_json({
'type': dest,
'rawHTML': rawHTML
})

async def sendNarration(rawHTML, update=False):
await sendDisplay('narrative' if not update else 'narrativeUpdate', rawHTML)

async def sendPlayerEcho(rawHTML):
await sendDisplay('playerEcho', rawHTML)

await websocket.accept()
myID = uuid.uuid4().hex

# Register connection with empty data
with conns_locks.gen_wlock():
conns[myID] = {
}

try:
await sendDisplay('header', 'Testing Header Text')

while True:
await sendNarration('Something <i>interesting</i> happened.')
startedWaitingAtMS = round((time.monotonic_ns()/1_000_000))
# TODO bit of a confused mess but lots to adapt in this loop
while round((time.monotonic_ns()/1_000_000)) < startedWaitingAtMS+5000:
try:
resp = await asyncio.wait_for(websocket.receive_json(), timeout=1/simRefreshRate)
await sendPlayerEcho('> ' + html.escape(resp['playerInput']))
break
except asyncio.exceptions.TimeoutError:
# timeout
await sendNarration('Something <i>EVEN MORE interesting</i> happened.', update=True)
except Exception as e:
traceback.print_exc()
# Remove websocket from connections if still there
debug ('Removing a connection from list')
with conns_locks.gen_wlock():
conns.pop(myID, None)

Cargando…
Cancelar
Guardar