# This file is part of MarMOTS. # # MarMOTS is free software: you can redistribute it and/or modify it under the terms of the GNU Affero # General Public License as published by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # MarMOTS is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General # Public License for more details. # # You should have received a copy of the GNU Affero General Public License along with MarMOTS. If not, # see . # # Copyright 2009, 2010, 2011, 2020 Jeremy Penner import stackless import greenlet import random import logging import pickle import time import ansi_cython as 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.queueValue = [] 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) return rgdg[rgev.index(ev)](value) def fire(self, value = None): self.queueValue.append(value) self.game.queueEv.append(self) self.game.ensureRun() def fireI(self): rgtkOld = self.rgtk value = self.queueValue[0] self.queueValue = self.queueValue[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 != greenlet.GreenletExit: 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 greenlet.GreenletExit: # 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