소스 검색

Add simulator loop example

master
fenceFoil 4 년 전
부모
커밋
699b13cdd2
5개의 변경된 파일180개의 추가작업 그리고 2개의 파일을 삭제
  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 파일 보기

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

+ 2
- 0
src/envVars.bat 파일 보기

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

+ 3
- 1
src/requirements.txt 파일 보기

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

+ 3
- 0
src/simulatorSampleApp.bat 파일 보기

@@ -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 파일 보기

@@ -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)

불러오는 중...
취소
저장