Commits

beyzend committed 039902f Draft

-A big update:
--proof-of-concept for long running and memory resident process on appengine backends resident server.
--THe idea is that we can run a game server using appengine backends.
--gameserver is a resident backend server
The idea here is that we treat the gameserver module as a singleton GameServer instance.
We start the server from /ah/start call at app startup.
From this startup process we start the game server as an appengine deferred task.
From ther the game server will run forever. It will handle instance quits, etc.
-Just a proof-of-concept. Still need lots of testing.

Comments (0)

Files changed (13)

-application: projectace3
+application: projectace3staging
 version: 1
 runtime: python
 api_version: 1
 
 builtins:
 - remote_api: on
+- deferred: on
 
 handlers:
 
 # PyAMF Flash Remoting Gateway
-#- url: /gateway
-#  script: gateway.py    
+- url: /gateway
+  script: gateway.py    
 
 # Static: Flash files.
 - url: /swfs
+backends:
+- name: gameserver
+  instances: 1
+  start: gameserver/startserver.py

datastore_script.py

 class DataStoreInit():
     def __init__(self):
         self.mob_spawns =[]
-        self.MAX_ENTITY = 50
+        self.MAX_ENTITY = 10
         self.START_REGION=u'START_REGION_KEY'
         return
     
         #randomize spawns
         #spawn_sample = random.sample(self.mob_spawns, self.MAX_ENTITY)
         for i in range(0, self.MAX_ENTITY):
-            spawn = random.choice(self.mob_spawns)
+            if(i % len(self.mob_spawns) == 0):
+                random.shuffle(self.mob_spawns)
+            
+            #spawn = random.choice(self.mob_spawns)
             amodel = MobEntity(homebase=map)
-        
+            spawn = self.mob_spawns[i % len(self.mob_spawns)]
+            print "SpawnName: ", spawn['name']
             self.writeModelDefaultState(amodel, spawn['x'], spawn['y'])
             
     

examples/__init__.py

 # Google App Engine: http://code.google.com/appengine/
 # PyAMF: http://pyamf.org
 
+from google.appengine.ext.webapp import template
+
 import wsgiref.handlers
 from google.appengine.ext import webapp
-from google.appengine.ext.webapp import template
 
 import logging
 from urlparse import urlparse

examples/model.py

 	hasPhoto = db.BooleanProperty(default=False)
 	createdAt = db.DateTimeProperty(auto_now_add=True)
 	modifiedAt = db.DateTimeProperty(auto_now=True)
+
+class AppState(db.Model):
+	currentState = db.IntegerProperty(2) #1 is start state, 2 is end state
+	
+	
 """
 MapRegion.
 """
 	health = db.IntegerProperty()
 	thinktime = db.FloatProperty()
 	timesincesync = db.DateTimeProperty() #use to determine whether to reset to original state.
-	timerespawn = db.DateTimeProperty()
-	ordercount = db.IntegerProperty() #for testing only. There is not transactions here.
 	
 	
 	

gameserver/GameServer.py

+
+from google.appengine.ext import webapp
+from google.appengine.runtime import DeadlineExceededError
+from google.appengine.ext import deferred
+import logging
+
+"""
+This module acts as a singleton GameServer class. 
+It is started from GameServer backend instance's start handler with a deferred runner.
+"""
+
+"""
+module 'global' variables
+"""
+import math as math
+from random import *
+import time
+
+
+maxMobs = 100
+mobs = []
+batchSize = 100000
+
+MEAN_TIME= 1.0 / 4.0 #6 seconds mean think time using a exp. distribution random process.
+
+def getRandomDir():
+    theta = random() * math.pi
+    phi = random() * math.pi
+    randomDir = (math.cos(theta), math.sin(phi))
+    return randomDir
+
+
+def getThinkTime():
+    return max(2.0, -math.log(1.0 - random()) / MEAN_TIME)
+
+
+def initMobs():
+    global mobs
+    for i in range(0, maxMobs):
+        mobs.append(getThinkTime())
+
+def rerun():
+    deferred.defer(run)
+
+def run():
+    global mobs
+    try:
+        for i in range(0, len(mobs)):
+            mobs[i] = getThinkTime()
+            
+    except DeadlineExceededError:
+        #clean up and rerun, this GameServer is run forever.
+        rerun()
+    rerun()
+
+def getCountService(idx):
+    if(idx >= maxMobs):
+        idx = maxMobs - 1 
+    return [idx, mobs[idx]]

gameserver/__init__.py

+'''
+Created on Sep 26, 2012
+
+@author: gnulinux
+'''

gameserver/startserver.py

+'''
+Created on Sep 26, 2012
+
+@author: gnulinux
+'''
+
+from google.appengine.dist import use_library
+use_library('django', '1.2')
+
+import wsgiref.handlers
+from google.appengine.ext import webapp
+
+from google.appengine.ext import deferred
+import gameserver.GameServer as GameServer
+
+class StartHandler(webapp.RequestHandler):
+    def get(self):
+        GameServer.initMobs()
+        deferred.defer(GameServer.run)
+        #deferred.defer(gameServer.run)
+        
+def main():
+    application = webapp.WSGIApplication([
+        ('/_ah/start', StartHandler)
+    ], debug=True)
+    wsgiref.handlers.CGIHandler().run(application)
+    
+if __name__ == '__main__':
+    main()
 
 # PyAMF Flash Remoting (RPC) gateway.
 
+from google.appengine.dist import use_library
+use_library('django', '1.2')
+
 import logging
 import wsgiref.handlers
 
 from google.appengine.ext.webapp.util import run_wsgi_app
 
 #from pyamf.remoting.gateway.wsgi import WSGIGateway
-from pyamf.remoting.gateway.google import WebAppGateway
+#from pyamf.remoting.gateway.google import WebAppGateway
+from pyamf.remoting.gateway.wsgi import WSGIGateway
+#from pyamf.remoting.gateway.google import WebAppGateway
 # You can also use a wildcard to import services here.
 #from services import user
 #from services import photo
 from services import mob
+from services.app import getGameServer
+from gameserver.GameServer import getCountService
 # Service mappings
 
 debug_enabled=True
+
 s = {
 	#'user': user,
 	#'photo': photo
 	'mob': mob.mob
+	,'mob.allmobs': mob.allmobs
+	, 'gameserver.gameserver': getCountService
+	, 'getgameserver': getGameServer
 	}
+
+"""
 gateway = WebAppGateway(s, logger=logging, debug=debug_enabled)
 application_path=[('', gateway)]
 application = webapp.WSGIApplication(application_path
 
 if __name__ == '__main__':
   main()
-"""
 #
 ######################################################################
 
+from google.appengine.dist import use_library
+use_library('django', '1.2')
+
+from google.appengine.ext.webapp import template
+
 import wsgiref.handlers
 from google.appengine.ext import webapp
-from google.appengine.ext.webapp import template
-from google.appengine.ext.webapp.util import run_wsgi_app
 
 
 from urlparse import urlparse
 
 from pyamf.remoting.gateway.google import WebAppGateway
 from services import mob
+from services import app
 
 class IndexHandler(webapp.RequestHandler):
 
 
 #NO WEBAPP2 installed. Upgrade GAE!
 debug_enabled=False
+"""
 s = {
 	#'user': user,
 	#'photo': photo
 	'mob': mob.mob
 	,'mob.allmobs': mob.allmobs
 	, 'mob.updatemob': mob.updateMob
+	, 'gameserver.gameserver': app.getGameServer
 	}
 gateway = WebAppGateway(s, logger=logging, debug=debug_enabled)
+"""
 
+"""
 application = webapp.WSGIApplication([
 		('/', IndexHandler),
 		('/gateway', gateway),
 
 if __name__== '__main__':
 	main()
+
 """
+
+
 def main():
 		
 	application = webapp.WSGIApplication([
 		('/', IndexHandler),
+		#('/gateway', gateway),
 		#('/test', TestUploadHandler),
 		#('/photo/upload', photo.PhotoUploadHandler),
 		#('/photo/download', photo.PhotoDownloadHandler),
 		('/examples/initial/flash(/.*)?', InitialFlashExample),
+		#('/gameserver', StartHandler),
 		#('/examples/initial/flex(/.*)?', InitialFlexExample),
 		('/.*', NotFoundHandler)
 	], debug=True)
 
 if __name__ == '__main__':
   main()
-"""
+queue:
+- name: default
+  rate: 1/s
+'''
+Created on Sep 25, 2012
+
+@author: gnulinux
+'''
+import logging
+
+
+import math as math
+from random import *
+import datetime as datetime
+
+from google.appengine.api import backends
+from google.appengine.api import urlfetch
+
+
+from examples.model import MobEntity 
+
+from examples.model import AppState
+
+def appStateChange(appState):
+    #logging.info("App State Changing Service: state changing to: ", appState)
+    appState =  AppState.all().fetch(1)[0]
+    #Shit this is kind of wrong. We're going to start the deferred tasks here.
+    #Be aware tho. we're not checking to see if defererd tasks are still running.
+    if appState == "START":
+        appState.currentState = 1
+    elif appState == "END":
+        appState.currentState = 2
+    appState.put()
+    
+def getGameServer():
+    url = "hello"
+    url = '%s/gameserver/' % (backends.get_url('gameserver'))
+    #count = urlfetch.fetch(url, method='POST').content
+    
+    return url
+    
 """
 This class defines a Mob controller class.
 """
-from pyamf import flex
-from pyamf import amf3
+#from pyamf import flex
+#from pyamf import amf3
 
-import math as math
-from random import *
 import datetime as datetime
 
+from pyamf.remoting.client import RemotingService
 
 from examples.model import MobEntity 
+from google.appengine.api import backends
+from google.appengine.api import urlfetch
 
 
 XRANGE=32*32 #there are 32 tiles in the first test map
 YRANGE=32*32
-MEAN_TIME= 1.0 / 6.0 #6 seconds mean think time using a exp. distribution random process.
-
-
   
 
 def mob(id):
+    url = '%s/' % (backends.get_url('gameserver'))
+    gateway = RemotingService(url+"gateway")
+    service = gateway.getService('gameserver.gameserver')
+    retArray = service(id)
+    return retArray
     #assume currentPos is a tuple representing a vector2 where in the order is x,y
     #assume a Poisson variable
     #return {"id":id, "randtime":-log(1.0 - random.random()) / MEAN_TIME}
-    return [id, -math.log(1.0 - random()) / MEAN_TIME]
+    return [0, 0]
 
-def getThinkTime():
-    return max(0.5, -math.log(1.0 - random()) / MEAN_TIME)
 
-def getRandomDir():
-    theta = random() * math.pi
-    phi = random() * math.pi
-    randomDir = (math.cos(theta), math.sin(phi))
-    return randomDir
-
-def checkAndResetMob(mob, timenow, elapsed, sinceRespawn):
-    mob.id = mob.key().id()
-    #if this time is greater than timeout, reset
-    if(float(elapsed) > 50.0 or float(sinceRespawn) > 50.0):
-            mob.pos_x = mob.spawn_x
-            mob.pos_y = mob.spawn_y
-            mob.thinktime = getThinkTime()
-            mob.timesincesync = timenow
-            mob.timerespawn = timenow
-            mob.put()
-            return True
-    return False
 
 def allmobs():
     #this method will get mobs from db.
     mobs = MobEntity.all().fetch(100) #should we hardcode limit for maps per map?
-    #we need to update think time
-    for mob in mobs:
-        timenow = datetime.datetime.now()
-        elapsed = (timenow - mob.timesincesync).seconds
-        sinceRespawn = (timenow - mob.timerespawn).seconds
-        checkAndResetMob(mob, timenow, elapsed, 0)
-            #actually need to reset position also
-        
+   
     return mobs
-#NO INTERPOLATION DONE!!! CAN BECOMEOUT OF SYNC!
-def writeMobToClientSide(mob, mobcs):
-    #we need to update the sync time to reflect elapsed time.
-    timenow = datetime.datetime.now()
-    elapsed = timenow - mob.timesincesync
-    #we can continuesly be out of sync
-    elapsedInSeconds = (float(elapsed.seconds) + float(elapsed.microseconds) / 1000000.0)
-    if(elapsedInSeconds <= mob.thinktime):
-        mobcs['thinktime'] = mob.thinktime  - elapsedInSeconds #conver to seconds
-    else:
-        mobcs['thinktime'] = 0.1; 
-    mobcs['pos_x'] = mob.pos_x
-    mobcs['pos_y'] = mob.pos_y
-    mobcs['dir_x'] = mob.dir_x
-    mobcs['dir_y'] = mob.dir_y
-    #mobcs['acceleration_x'] = mob.acceleration_x
-    #mobcs['acceleration_y'] = mob.acceleration_y
-    mobcs['health'] = mob.health
-    mobcs['timesincesync'] = mob.timesincesync
-    mobcs['ordercount'] = mob.ordercount
-    mobcs['id'] = mob.key().id()
 
-def updateServerMob(mob, mobcs):
-    #think up new think time
-    mob.ordercount += long(1)
-    mob.thinktime = getThinkTime()
-    mob.timesincesync = datetime.datetime.now()
-    mob.pos_x = float(mobcs['pos_x'])
-    mob.pos_y = float(mobcs['pos_y'])
-    #get random direction
-    randDir = getRandomDir()
-    dirTheta = math.atan2(mob.dir_y * -1.0, mob.dir_x * -1.0);
-    mob.dir_x = randDir[0] * math.cos(dirTheta) - randDir[1] * math.sin(dirTheta)
-    mob.dir_y = randDir[1] * math.cos(dirTheta) + randDir[0] * math.sin(dirTheta)
-    #mob.acceleration_x = 
-    
-def updateMob(clientId, mobClientSide):
-    mob = MobEntity.get_by_id(mobClientSide['id'])
-    timenow = datetime.datetime.now()
-    elapsed = (timenow - mob.timesincesync).seconds
-    sinceRespawn = (timenow - mob.timerespawn).seconds
-    if checkAndResetMob(mob, timenow, elapsed, sinceRespawn) == True:
-        return [clientId, mob]
-    #if ordercount is less 
-    #if (mobClientSide['ordercount'] < mob.ordercount): #this means someone before this client has already update the server view. refresh client.
-    if (mobClientSide['timesincesync'] < mob.timesincesync):
-        writeMobToClientSide(mob, mobClientSide)
-        return [clientId, mobClientSide]
-    #Due to the way the calls are a Poisson process, they're semi ordered!
-    #So who ever gets here gets to update server's view. They may become out of sync. But we'll need to eventually
-    #make a MOB go back to spawn point. We can also do server side prediction to mitigate any problems.
-    updateServerMob(mob, mobClientSide)
-    mob.put()
-    mob.id = mob.key().id()
-    return [clientId, mob] 
-    
-    
-