marmots/engine.py

551 lines
15 KiB
Python

import stackless
import random
import logging
import pickle
import time
import ansi
import auth
import config
import telnet
import traceback
from twisted.internet import reactor
from tpers import TPrs, TPLS, Version
import tpers
class Event(TPrs):
def InitPersistent(self, game):
self.game = game
def InitTransient(self):
self.rgtk = TPLS()
self.stkValue = []
def oob(self, tok, dgoob):
return Oob(self, tok, dgoob)
def receive(self, tok):
chNotify = tok.chEv
self.rgtk.append(tok)
while True:
ev, value = chNotify.receive()
if ev == self:
return value
ev.rgtk.append(tok)
if ev in tok.mpevDgoob and tok.mpevDgoob[ev](value):
raise Event.OobBreakException()
@classmethod
def select(cls, tok, *rgev):
for ev in rgev:
ev.rgtk.append(tok)
chNotify = tok.chEv
fOob = False
while True:
evRcv, value = chNotify.receive()
if evRcv in tok.mpevDgoob:
if tok.mpevDgoob[ev](value):
fOob = True
break
else:
break
for ev in rgev:
if ev != evRcv:
ev.rgtk.remove(tok)
if fOob:
raise Event.OobBreakException()
return evRcv, value
@classmethod
def selectDg(cls, tok, *rgev_dg):
rgev, rgdg = zip(*rgev_dg)
ev, value = cls.select(tok, *rgev)
rgdg[rgev.index(ev)](value)
def fire(self, value = None):
self.stkValue.append(value)
self.game.queueEv.append(self)
self.game.ensureRun()
def fireI(self):
rgtkOld = self.rgtk
value = self.stkValue[0]
self.stkValue = self.stkValue[1:]
self.rgtk = TPLS()
for tk in rgtkOld:
if tk.fRunning:
tk.chEv.send((self,value))
stackless.run()
class Oob(object):
def __init__(self, ev, tok, dgoob):
self.ev = ev
self.tok = tok
self.dgoob = dgoob
def __enter__(self):
self.ev.rgtk.append(self.tok)
self.tok.mpevDgoob[self.ev] = self.dgoob
def __exit__(self, etype, eval, tb):
if etype != TaskletExit:
try:
self.tok.mpevDgoob.pop(self.ev)
self.ev.rgtk.remove(self.tok)
if etype == OobBreakException:
return True
except Exception:
print "exception exiting oob:"
traceback.print_exc()
print "oob:", etype, eval
traceback.print_tb(tb)
class OobBreakException(Exception):
pass
class Runner(TPrs):
def InitPersistent(self):
self.mpuser_cld = {}
print "New Runner created"
def InitTransient(self):
self.rgclient = []
def GetCld(self, user):
cld = self.mpuser_cld.get(user)
if cld == None:
cld = Cld(user)
self.mpuser_cld[user] = cld
return cld
def RunGameI(self, client):
client.runner = self
self.rgclient.append(client)
def OnDisconnect():
self.rgclient.remove(client)
client.addDgQuit(OnDisconnect)
return self.RunGame(client)
def RunServer(self):
for client in self.rgclient:
client.quit(True)
telnet.RunServer(self.RunGameI)
def Run(clsRunner, fn):
runner = tpers.Odb.Load(fn) or clsRunner()
def ExecCmd(stCmd):
if stCmd == "save":
tpers.Odb.Save(runner, fn)
return "Saved to " + fn
return "Unrecognized command: '" + stCmd + "'"
telnet.RunCmdServer(ExecCmd)
runner.RunServer()
class Ownable(TPrs):
def InitPersistent(self, owner = None):
self.owner = owner
self.rgtokOwn = TPLS()
if owner != None:
owner.rgtokOwn.append(self)
def FPersist(self):
if self.owner != None and not self.owner.FPersist():
return False
return TPrs.FPersist(self)
def TransferToOwner(self, ownerNew):
self.owner.rgtokOwn.remove(self)
ownerNew.rgtokOwn.append(self)
self.owner = ownerNew
def die(self):
if self.owner != None:
self.owner.rgtokOwn.remove(self)
self.owner = None
for tok in [x for x in self.rgtokOwn]:
tok.die()
self.DontPersist()
@Version(2)
class Game(Ownable):
def InitPersistent(self):
Ownable.InitPersistent(self)
self.fQuit = False
self.running = False
self.mptagTokenset = {}
self.rgdgdie = []
self.evJoin = Event(self)
self.evLeave = Event(self)
self.mpuser_cldg = {}
self.CreateRgtok(self.GetRgclsTokPers(), True)
def InitTransient(self):
self.MakeTransient("fScheduled", False)
self.rgclient = []
self.rgproc = [PTimer(), PEvent(), PDraw(*self.GetRgtagDraw())]
for proc in self.rgproc:
proc.setGame(self)
# don't recreate tokens when game has died (shouldn't even be persisting)
if not self.FDead():
self.CreateRgtok(self.GetRgclsTokTrans(), False)
else:
print "Dead", self.__class__, "has persisted"
def FDead(self):
try:
rc = self.rc
return True
except:
return False
def GetCldg(self, user):
cldg = self.mpuser_cldg.get(user)
if cldg == None:
cldg = Cldg()
self.mpuser_cldg[user] = cldg
return cldg
def GetRgclsTokPers(self):
return []
def GetRgclsTokTrans(self):
return []
def GetRgtagDraw(self):
return ["background", "solid", "overlay"]
def CreateRgtok(self, rgclsTok, fPersist):
for clsTok in rgclsTok:
if FIter(clsTok):
tok = clsTok[0](self, *clsTok[1:])
else:
tok = clsTok(self)
if not fPersist:
tok.DontPersist()
def tagToken(self, token, tag):
if not (tag in self.mptagTokenset):
self.mptagTokenset[tag] = set([token])
else:
self.mptagTokenset[tag].add(token)
def untagToken(self, token, tag):
if tag in self.mptagTokenset:
self.mptagTokenset[tag].discard(token)
def rgtoken(self, tag, rgpriority = []):
if not (tag in self.mptagTokenset):
return []
rgtoken = []
tokenSet = self.mptagTokenset[tag].copy()
for priority in rgpriority:
if (priority in self.mptagTokenset):
ptokenSet = self.mptagTokenset[priority] & tokenSet
for token in ptokenSet:
rgtoken.append(token)
tokenSet -= ptokenSet
for token in tokenSet:
rgtoken.append(token)
return rgtoken
def quit(self):
self.running = False
self.fQuit = True
def finish(self, rc = None):
self.running = False
self.rc = rc
class QuitException(Exception):
pass
class LoadException(Exception):
pass
def die(self):
for dgdie in self.rgdgdie:
dgdie()
Ownable.die(self)
def runStep(self):
try:
if self.running:
for proc in self.rgproc:
proc.process()
if not self.running:
self.die()
for client in self.rgclient:
client.leaveGame(self.rc)
for client in self.rgclient:
client.postRunStep()
finally:
self.fScheduled = False
return self.running
def joinGame(self,client):
self.rgclient.append(client)
self.evJoin.fire(client)
def leaveGame(self, client):
self.evLeave.fire(client)
def leaveGameI(self, client):
self.rgclient.remove(client)
def ensureRun(self):
self.running = True
if not self.fScheduled:
self.fScheduled = True
reactor.callLater(0, self.runStep)
def UpgradeFrom(self, versionOld):
if versionOld < 2:
self.mpuser_cldg = {}
# Client data
@Version(2)
class Cld(TPrs):
def InitPersistent(self, user):
self.user = user
def UpgradeFrom(self, versionOld):
if versionOld < 2:
del self.mpgame_cldg
#per-game client data
class Cldg(TPrs):
def FPersist(self):
if len(self._persistent) == 0:
return False
return TPrs.FPersist(self)
class Client(TPrs):
def InitTransient(self):
self.chRun = stackless.channel()
self.gameCurr = None
self.chCont = None
self.fRunning = False
self.dgJoinGame = None
self.rgdgQuit = []
self.fQuit = False
self.cld = None
self.runner = None
def FPersist(self):
return False
def go(self, fnRun, protocol):
self.protocol = protocol
self.tskRun = stackless.tasklet(fnRun)
self.tskRun(self)
if self._runTskRun():
self.gameCurr.ensureRun()
def joinGame(self, game):
assert not self.fRunning, "already running"
chCont = stackless.channel()
self.chRun.send((game, chCont))
return chCont.receive()
def leaveGame(self, rc = None):
self.fRunning = False
self.gameCurr.leaveGame(self)
self.rc = rc
def quit(self):
self.leaveGame(None)
self.fQuit = True
def _runTskRun(self):
"run self.tskRun after it has been scheduled, and await results"
stackless.run()
if not self.tskRun.scheduled:
return False
self.gameCurr, self.chCont = self.chRun.receive()
self.gameCurr.joinGame(self)
self.fRunning = True
if self.dgJoinGame:
self.dgJoinGame(self, self.gameCurr)
return True
def postRunStep(self):
if not self.fRunning:
self.gameCurr.leaveGameI(self)
if not self.fQuit:
self.chCont.send(self.rc)
self.fQuit = not self._runTskRun()
if self.fQuit:
for dgQuit in self.rgdgQuit:
dgQuit()
return self.fQuit
def beep(self):
pass
def login(self, user, passwd):
authr = getattr(auth, config.AUTH + "Auth")()
if (authr.FAuthorized(user, passwd)):
self.cld = self.runner.GetCld(user)
return True
return False
def addDgQuit(self, dgquit):
self.rgdgQuit.append(dgquit)
def removeDgQuit(self, dgquit):
self.rgdgQuit.remove(dgquit)
def Cldg(self, game = None):
if game == None:
game = self.gameCurr
return game.GetCldg(self.cld.user)
class Processor(TPrs):
def setGame(self, game):
self.game = game
def process(self):
pass
class PTimer(Processor):
def msNow(self):
return int(time.time() * 1000)
def setGame(self, game):
self.game = game
self.fReset = False
game.evTick = Event(game)
game.ms = 0
game.msDelta = 0
self.dms = -self.msNow()
def __setstate__(self, dict):
Processor.__setstate__(self, dict)
self.fReset = True
def process(self):
if self.fReset:
# keep game.ms constant
self.dms = self.game.ms - self.msNow()
self.fReset = False
msNew = self.msNow() + self.dms
self.game.msDelta = msNew - self.game.ms
self.game.ms = msNew
self.game.evTick.fire(self.game.msDelta)
class PEvent(Processor):
def setGame(self, game):
self.game = game
game.queueEv = []
game.rgdgdie.append(self.die)
def die(self):
for ev in self.game.queueEv:
for tk in ev.rgtk:
tk.die()
ev.stkValue = []
self.game.queueEv = []
def process(self):
while self.game.queueEv:
self.game.queueEv[0].fireI()
self.game.queueEv.pop(0)
class PDraw(Processor):
def InitPersistent(self, *rgprio):
self.rgprio = rgprio
self.msDrawAgain = -1
self.fTimerSet = False
def process(self):
if self.game.ms >= self.msDrawAgain:
for client in self.game.rgclient:
ascr = ansi.Ascr()
for tok in self.game.rgtoken("drawable", self.rgprio):
try:
tok.draw(ascr, client)
except Exception:
print "error drawing"
traceback.print_exc()
client.protocol.Draw(ascr)
self.msDrawAgain = self.game.ms + 120
self.fTimerSet = False
elif not self.fTimerSet:
reactor.callLater((self.msDrawAgain - self.game.ms) / 1000.0, self.game.ensureRun)
self.fTimerSet = True
class Taggable(Ownable):
def InitPersistent(self, owner, *rgtag):
Ownable.InitPersistent(self, owner)
game = owner
while not isinstance(game, Game):
game = game.owner
self.game = game
self.tags = set()
self.tag(*rgtag)
def tag(self, *rgtag):
self.tags |= set(rgtag)
for tag in rgtag:
self.game.tagToken(self, tag)
def untag(self, *rgtag):
self.tags -= set(rgtag)
for tag in rgtag:
self.game.untagToken(self, tag)
def die(self):
for tag in self.tags.copy():
self.untag(tag)
Ownable.die(self)
class Token(Taggable):
def InitPersistent(self, owner, *rgtag):
Taggable.InitPersistent(self, owner, *rgtag)
self.msGamePreWait = 0
self.ms = 0
self.fRunning = True
self.rgtokOwn = TPLS()
def InitTransient(self):
Taggable.InitTransient(self)
self.chEv = stackless.channel()
self.tasklet = stackless.tasklet(self.runI)
self.mpevDgoob = {}
self._fWriteToPersistent = True
self.tasklet()
def __enter__(self):
return self
def __exit__(self, etype, eval, traceback):
self.die()
def wait(self, msDelta):
# have we waited once already this tick? If not, reset
if (self.msGamePreWait != self.game.ms):
self.ms = self.game.ms
msEnd = self.ms + msDelta
delay = (msEnd - self.game.ms) / 1000.0
if delay > 0:
reactor.callLater(delay, self.game.ensureRun)
while (self.game.ms < msEnd):
self.game.evTick.receive(self)
self.ms = msEnd
self.msGamePreWait = self.game.ms
def die(self):
self.fRunning = False
Taggable.die(self)
self.tasklet.kill() # the token may be killing itself; make sure it's properly marked as dead before killing the tasklet
def run(self):
pass
def runI(self):
try:
self.run()
#except TaskletExit:
# raise
except Exception:
print "token script crashed:"
traceback.print_exc()
def FIter(l):
try:
iter(l)
return True
except Exception:
return False
class TokenClient(Token):
def InitPersistent(self, owner, client, *rgtag):
Token.InitPersistent(self, owner, *rgtag)
self.client = client
def FPersist(self):
return False