Added a timeout to the attribute caching; the system will now clean cache at regular intervals once it pass a certain size defined in settings.
This commit is contained in:
parent
e3ce0a7933
commit
3091587e33
8 changed files with 137 additions and 106 deletions
|
|
@ -141,8 +141,10 @@ class Command(object):
|
||||||
input can be either a cmd object or the name of a command.
|
input can be either a cmd object or the name of a command.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
|
# first assume input is a command (the most common case)
|
||||||
return cmd.key in self._matchset
|
return cmd.key in self._matchset
|
||||||
except AttributeError: # got a string
|
except AttributeError:
|
||||||
|
# probably got a string
|
||||||
return cmd in self._matchset
|
return cmd in self._matchset
|
||||||
|
|
||||||
def __contains__(self, query):
|
def __contains__(self, query):
|
||||||
|
|
@ -154,7 +156,7 @@ class Command(object):
|
||||||
query (str) - query to match against. Should be lower case.
|
query (str) - query to match against. Should be lower case.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return any(query in keyalias for keyalias in self._matchset)
|
return any(query in keyalias for keyalias in self._keyaliases)
|
||||||
|
|
||||||
def match(self, cmdname):
|
def match(self, cmdname):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ from src.server.sessionhandler import SESSIONS
|
||||||
from src.scripts.models import ScriptDB
|
from src.scripts.models import ScriptDB
|
||||||
from src.objects.models import ObjectDB
|
from src.objects.models import ObjectDB
|
||||||
from src.players.models import PlayerDB
|
from src.players.models import PlayerDB
|
||||||
from src.utils import logger, utils, gametime
|
from src.utils import logger, utils, gametime, create
|
||||||
from src.commands.default.muxcommand import MuxCommand
|
from src.commands.default.muxcommand import MuxCommand
|
||||||
|
|
||||||
# limit symbol import for API
|
# limit symbol import for API
|
||||||
|
|
@ -213,9 +213,10 @@ class CmdScripts(MuxCommand):
|
||||||
Operate on scripts.
|
Operate on scripts.
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
@scripts[/switches] [<obj or scriptid>]
|
@scripts[/switches] [<obj or scriptid or script.path>]
|
||||||
|
|
||||||
Switches:
|
Switches:
|
||||||
|
start - start a script (must supply a script path)
|
||||||
stop - stops an existing script
|
stop - stops an existing script
|
||||||
kill - kills a script - without running its cleanup hooks
|
kill - kills a script - without running its cleanup hooks
|
||||||
validate - run a validation on the script(s)
|
validate - run a validation on the script(s)
|
||||||
|
|
@ -225,9 +226,11 @@ class CmdScripts(MuxCommand):
|
||||||
will be searched for all scripts defined on it, or an script name
|
will be searched for all scripts defined on it, or an script name
|
||||||
or dbref. For using the /stop switch, a unique script dbref is
|
or dbref. For using the /stop switch, a unique script dbref is
|
||||||
required since whole classes of scripts often have the same name.
|
required since whole classes of scripts often have the same name.
|
||||||
|
|
||||||
|
Use @script for managing commands on objects.
|
||||||
"""
|
"""
|
||||||
key = "@scripts"
|
key = "@scripts"
|
||||||
aliases = "@listscripts"
|
aliases = ["@globalscript", "@listscripts"]
|
||||||
locks = "cmd:perm(listscripts) or perm(Wizards)"
|
locks = "cmd:perm(listscripts) or perm(Wizards)"
|
||||||
help_category = "System"
|
help_category = "System"
|
||||||
|
|
||||||
|
|
@ -239,6 +242,14 @@ class CmdScripts(MuxCommand):
|
||||||
|
|
||||||
string = ""
|
string = ""
|
||||||
if args:
|
if args:
|
||||||
|
if "start" in self.switches:
|
||||||
|
# global script-start mode
|
||||||
|
new_script = create.create_script(args)
|
||||||
|
if new_script:
|
||||||
|
caller.msg("Global script %s was started successfully." % args)
|
||||||
|
else:
|
||||||
|
caller.msg("Global script %s could not start correctly. See logs." % args)
|
||||||
|
return
|
||||||
|
|
||||||
# test first if this is a script match
|
# test first if this is a script match
|
||||||
scripts = ScriptDB.objects.get_all_scripts(key=args)
|
scripts = ScriptDB.objects.get_all_scripts(key=args)
|
||||||
|
|
|
||||||
|
|
@ -5,17 +5,22 @@ scripts are inheriting from.
|
||||||
It also defines a few common scripts.
|
It also defines a few common scripts.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from sys import getsizeof
|
||||||
from time import time
|
from time import time
|
||||||
|
from collections import defaultdict
|
||||||
from twisted.internet.defer import maybeDeferred
|
from twisted.internet.defer import maybeDeferred
|
||||||
from twisted.internet.task import LoopingCall
|
from twisted.internet.task import LoopingCall
|
||||||
from twisted.internet import task
|
from django.conf import settings
|
||||||
from src.server.sessionhandler import SESSIONS
|
from src.server.sessionhandler import SESSIONS
|
||||||
from src.typeclasses.typeclass import TypeClass
|
from src.typeclasses.typeclass import TypeClass
|
||||||
|
from src.typeclasses.models import _ATTRIBUTE_CACHE
|
||||||
from src.scripts.models import ScriptDB
|
from src.scripts.models import ScriptDB
|
||||||
from src.comms import channelhandler
|
from src.comms import channelhandler
|
||||||
from src.utils import logger
|
from src.utils import logger
|
||||||
|
|
||||||
__all__ = ("Script", "DoNothing", "CheckSessions", "ValidateScripts", "ValidateChannelHandler", "AddCmdSet")
|
__all__ = ("Script", "DoNothing", "CheckSessions", "ValidateScripts", "ValidateChannelHandler", "ClearAttributeCache")
|
||||||
|
|
||||||
|
_ATTRIBUTE_CACHE_MAXSIZE = settings.ATTRIBUTE_CACHE_MAXSIZE # attr-cache size in MB.
|
||||||
|
|
||||||
#
|
#
|
||||||
# Base script, inherit from Script below instead.
|
# Base script, inherit from Script below instead.
|
||||||
|
|
@ -142,7 +147,7 @@ class ScriptClass(TypeClass):
|
||||||
if obj:
|
if obj:
|
||||||
# check so the scripted object is valid and initalized
|
# check so the scripted object is valid and initalized
|
||||||
try:
|
try:
|
||||||
dummy = object.__getattribute__(obj, 'cmdset')
|
object.__getattribute__(obj, 'cmdset')
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
# this means the object is not initialized.
|
# this means the object is not initialized.
|
||||||
self.dbobj.is_active = False
|
self.dbobj.is_active = False
|
||||||
|
|
@ -182,7 +187,7 @@ class ScriptClass(TypeClass):
|
||||||
if self.dbobj.db_interval > 0:
|
if self.dbobj.db_interval > 0:
|
||||||
try:
|
try:
|
||||||
self._stop_task()
|
self._stop_task()
|
||||||
except Exception, e:
|
except Exception:
|
||||||
logger.log_trace("Stopping script %s(%s)" % (self.key, self.dbid))
|
logger.log_trace("Stopping script %s(%s)" % (self.key, self.dbid))
|
||||||
pass
|
pass
|
||||||
try:
|
try:
|
||||||
|
|
@ -217,7 +222,7 @@ class ScriptClass(TypeClass):
|
||||||
self.ndb._paused_time = dt
|
self.ndb._paused_time = dt
|
||||||
self._start_task(start_now=False)
|
self._start_task(start_now=False)
|
||||||
del self.db._paused_time
|
del self.db._paused_time
|
||||||
except Exception, e:
|
except Exception:
|
||||||
logger.log_trace()
|
logger.log_trace()
|
||||||
self.dbobj.is_active = False
|
self.dbobj.is_active = False
|
||||||
return False
|
return False
|
||||||
|
|
@ -387,7 +392,7 @@ class DoNothing(Script):
|
||||||
def at_script_creation(self):
|
def at_script_creation(self):
|
||||||
"Setup the script"
|
"Setup the script"
|
||||||
self.key = "sys_do_nothing"
|
self.key = "sys_do_nothing"
|
||||||
self.desc = "This is a placeholder script."
|
self.desc = "This is an empty placeholder script."
|
||||||
|
|
||||||
class CheckSessions(Script):
|
class CheckSessions(Script):
|
||||||
"Check sessions regularly."
|
"Check sessions regularly."
|
||||||
|
|
@ -420,7 +425,6 @@ class ValidateScripts(Script):
|
||||||
|
|
||||||
class ValidateChannelHandler(Script):
|
class ValidateChannelHandler(Script):
|
||||||
"Update the channelhandler to make sure it's in sync."
|
"Update the channelhandler to make sure it's in sync."
|
||||||
|
|
||||||
def at_script_creation(self):
|
def at_script_creation(self):
|
||||||
"Setup the script"
|
"Setup the script"
|
||||||
self.key = "sys_channels_validate"
|
self.key = "sys_channels_validate"
|
||||||
|
|
@ -433,44 +437,16 @@ class ValidateChannelHandler(Script):
|
||||||
#print "ValidateChannelHandler run."
|
#print "ValidateChannelHandler run."
|
||||||
channelhandler.CHANNELHANDLER.update()
|
channelhandler.CHANNELHANDLER.update()
|
||||||
|
|
||||||
class AddCmdSet(Script):
|
class ClearAttributeCache(Script):
|
||||||
"""
|
"Clear the attribute cache."
|
||||||
This script permanently assigns a command set
|
|
||||||
to an object whenever it is started. This is not
|
|
||||||
used by the core system anymore, it's here mostly
|
|
||||||
as an example.
|
|
||||||
"""
|
|
||||||
def at_script_creation(self):
|
def at_script_creation(self):
|
||||||
"Setup the script"
|
"Setup the script"
|
||||||
if not self.key:
|
self.key = "sys_cache_clear"
|
||||||
self.key = "add_cmdset"
|
self.desc = "Clears the Attribute Cache"
|
||||||
if not self.desc:
|
self.interval = 3600 * 2
|
||||||
self.desc = "Adds a cmdset to an object."
|
|
||||||
self.persistent = True
|
self.persistent = True
|
||||||
|
def at_repeat(self):
|
||||||
# this needs to be assigned to upon creation.
|
"called every 2 hours. Sets a max attr-cache limit to 100 MB." # enough for normal usage?
|
||||||
# It should be a string pointing to the right
|
global _ATTRIBUTE_CACHE
|
||||||
# cmdset module and cmdset class name, e.g.
|
if getsizeof(_ATTRIBUTE_CACHE) / 1024.0 > _ATTRIBUTE_CACHE_MAXSIZE:
|
||||||
# 'examples.cmdset_redbutton.RedButtonCmdSet'
|
_ATTRIBUTE_CACHE = defaultdict(dict)
|
||||||
# self.db.cmdset = <cmdset_path>
|
|
||||||
# self.db.add_default = <bool>
|
|
||||||
|
|
||||||
def at_start(self):
|
|
||||||
"Get cmdset and assign it."
|
|
||||||
cmdset = self.db.cmdset
|
|
||||||
if cmdset:
|
|
||||||
if self.db.add_default:
|
|
||||||
self.obj.cmdset.add_default(cmdset)
|
|
||||||
else:
|
|
||||||
self.obj.cmdset.add(cmdset)
|
|
||||||
|
|
||||||
def at_stop(self):
|
|
||||||
"""
|
|
||||||
This removes the cmdset when the script stops
|
|
||||||
"""
|
|
||||||
cmdset = self.db.cmdset
|
|
||||||
if cmdset:
|
|
||||||
if self.db.add_default:
|
|
||||||
self.obj.cmdset.delete_default()
|
|
||||||
else:
|
|
||||||
self.obj.cmdset.delete(cmdset)
|
|
||||||
|
|
|
||||||
|
|
@ -135,7 +135,9 @@ def create_system_scripts():
|
||||||
script2 = create.create_script(scripts.ValidateScripts)
|
script2 = create.create_script(scripts.ValidateScripts)
|
||||||
# update the channel handler to make sure it's in sync
|
# update the channel handler to make sure it's in sync
|
||||||
script3 = create.create_script(scripts.ValidateChannelHandler)
|
script3 = create.create_script(scripts.ValidateChannelHandler)
|
||||||
if not script1 or not script2 or not script3:
|
# clear the attribute cache regularly
|
||||||
|
script4 = create.create_script(scripts.ClearAttributeCache)
|
||||||
|
if not script1 or not script2 or not script3 or not script4:
|
||||||
print _(" Error creating system scripts.")
|
print _(" Error creating system scripts.")
|
||||||
|
|
||||||
def start_game_time():
|
def start_game_time():
|
||||||
|
|
|
||||||
|
|
@ -99,10 +99,15 @@ IDLE_COMMAND = "idle"
|
||||||
ENCODINGS = ["utf-8", "latin-1", "ISO-8859-1"]
|
ENCODINGS = ["utf-8", "latin-1", "ISO-8859-1"]
|
||||||
# The game server opens an AMP port so that the portal can
|
# The game server opens an AMP port so that the portal can
|
||||||
# communicate with it. This is an internal functionality of Evennia, usually
|
# communicate with it. This is an internal functionality of Evennia, usually
|
||||||
# operating between the two processes on the same machine. Don't change unless
|
# operating between two processes on the same machine. You usually don't need to
|
||||||
# you know what you are doing.
|
# change this unless you cannot use the default AMP port/host for whatever reason.
|
||||||
AMP_HOST = 'localhost'
|
AMP_HOST = 'localhost'
|
||||||
AMP_PORT = 5000
|
AMP_PORT = 5000
|
||||||
|
# Attributes on objects are cached aggressively for speed. If the number of
|
||||||
|
# objects is large (and their attributes are often accessed) this can use up a lot of
|
||||||
|
# memory. So every now and then Evennia checks the size of this cache and resets
|
||||||
|
# it if it's too big. This variable sets the maximum size (in MB).
|
||||||
|
ATTRIBUTE_CACHE_MAXSIZE = 100
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
# Evennia Database config
|
# Evennia Database config
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,8 @@ try:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
import pickle
|
import pickle
|
||||||
import traceback
|
import traceback
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils.encoding import smart_str
|
from django.utils.encoding import smart_str
|
||||||
|
|
@ -77,6 +79,11 @@ def _del_cache(obj, name):
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# this cache holds the attributes loaded on objects, one dictionary
|
||||||
|
# of attributes per object.
|
||||||
|
_ATTRIBUTE_CACHE = defaultdict(dict)
|
||||||
|
|
||||||
|
|
||||||
#------------------------------------------------------------
|
#------------------------------------------------------------
|
||||||
#
|
#
|
||||||
# Attributes
|
# Attributes
|
||||||
|
|
@ -673,6 +680,7 @@ class TypeNickHandler(object):
|
||||||
#
|
#
|
||||||
#------------------------------------------------------------
|
#------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
class TypedObject(SharedMemoryModel):
|
class TypedObject(SharedMemoryModel):
|
||||||
"""
|
"""
|
||||||
Abstract Django model.
|
Abstract Django model.
|
||||||
|
|
@ -1213,18 +1221,17 @@ class TypedObject(SharedMemoryModel):
|
||||||
|
|
||||||
# Helper methods for persistent attributes
|
# Helper methods for persistent attributes
|
||||||
|
|
||||||
_attribute_cache = {}
|
|
||||||
def has_attribute(self, attribute_name):
|
def has_attribute(self, attribute_name):
|
||||||
"""
|
"""
|
||||||
See if we have an attribute set on the object.
|
See if we have an attribute set on the object.
|
||||||
|
|
||||||
attribute_name: (str) The attribute's name.
|
attribute_name: (str) The attribute's name.
|
||||||
"""
|
"""
|
||||||
if attribute_name not in _GA(self, "_attribute_cache"):
|
if attribute_name not in _ATTRIBUTE_CACHE[self]:
|
||||||
attrib_obj = _GA(self, "_attribute_class").objects.filter(db_obj=self).filter(
|
attrib_obj = _GA(self, "_attribute_class").objects.filter(db_obj=self).filter(
|
||||||
db_key__iexact=attribute_name)
|
db_key__iexact=attribute_name)
|
||||||
if attrib_obj:
|
if attrib_obj:
|
||||||
_GA(self, "_attribute_cache")[attribute_name] = attrib_obj[0]
|
_ATTRIBUTE_CACHE[self][attribute_name] = attrib_obj[0]
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
@ -1238,7 +1245,7 @@ class TypedObject(SharedMemoryModel):
|
||||||
new_value: (python obj) The value to set the attribute to. If this is not
|
new_value: (python obj) The value to set the attribute to. If this is not
|
||||||
a str, the object will be stored as a pickle.
|
a str, the object will be stored as a pickle.
|
||||||
"""
|
"""
|
||||||
attrib_obj = _GA(self, "_attribute_cache").get("attribute_name")
|
attrib_obj = _ATTRIBUTE_CACHE[self].get("attribute_name")
|
||||||
if not attrib_obj:
|
if not attrib_obj:
|
||||||
attrclass = _GA(self, "_attribute_class")
|
attrclass = _GA(self, "_attribute_class")
|
||||||
# check if attribute already exists.
|
# check if attribute already exists.
|
||||||
|
|
@ -1252,7 +1259,7 @@ class TypedObject(SharedMemoryModel):
|
||||||
attrib_obj = attrclass(db_key=attribute_name, db_obj=self)
|
attrib_obj = attrclass(db_key=attribute_name, db_obj=self)
|
||||||
# re-set an old attribute value
|
# re-set an old attribute value
|
||||||
attrib_obj.value = new_value
|
attrib_obj.value = new_value
|
||||||
_GA(self,"_attribute_cache")[attribute_name] = attrib_obj
|
_ATTRIBUTE_CACHE[self][attribute_name] = attrib_obj
|
||||||
|
|
||||||
def get_attribute(self, attribute_name, default=None):
|
def get_attribute(self, attribute_name, default=None):
|
||||||
"""
|
"""
|
||||||
|
|
@ -1263,14 +1270,14 @@ class TypedObject(SharedMemoryModel):
|
||||||
attribute_name: (str) The attribute's name.
|
attribute_name: (str) The attribute's name.
|
||||||
default: What to return if no attribute is found
|
default: What to return if no attribute is found
|
||||||
"""
|
"""
|
||||||
attrib_obj = _GA(self,"_attribute_cache").get(attribute_name)
|
attrib_obj = _ATTRIBUTE_CACHE[self].get(attribute_name)
|
||||||
if not attrib_obj:
|
if not attrib_obj:
|
||||||
attrib_obj = _GA(self, "_attribute_class").objects.filter(
|
attrib_obj = _GA(self, "_attribute_class").objects.filter(
|
||||||
db_obj=self).filter(db_key__iexact=attribute_name)
|
db_obj=self).filter(db_key__iexact=attribute_name)
|
||||||
if not attrib_obj:
|
if not attrib_obj:
|
||||||
return default
|
return default
|
||||||
_GA(self,"_attribute_cache")[attribute_name] = attrib_obj[0] #query is first evaluated here
|
_ATTRIBUTE_CACHE[self][attribute_name] = attrib_obj[0] #query is first evaluated here
|
||||||
return _GA(self, "_attribute_cache")[attribute_name].value
|
return _ATTRIBUTE_CACHE[self][attribute_name].value
|
||||||
return attrib_obj.value
|
return attrib_obj.value
|
||||||
|
|
||||||
def get_attribute_raise(self, attribute_name):
|
def get_attribute_raise(self, attribute_name):
|
||||||
|
|
@ -1280,14 +1287,14 @@ class TypedObject(SharedMemoryModel):
|
||||||
|
|
||||||
attribute_name: (str) The attribute's name.
|
attribute_name: (str) The attribute's name.
|
||||||
"""
|
"""
|
||||||
attrib_obj = _GA(self, "_attribute_cache.get")(attribute_name)
|
attrib_obj = _ATTRIBUTE_CACHE[self].get(attribute_name)
|
||||||
if not attrib_obj:
|
if not attrib_obj:
|
||||||
attrib_obj = _GA(self, "_attribute_class").objects.filter(
|
attrib_obj = _GA(self, "_attribute_class").objects.filter(
|
||||||
db_obj=self).filter(db_key__iexact=attribute_name)
|
db_obj=self).filter(db_key__iexact=attribute_name)
|
||||||
if not attrib_obj:
|
if not attrib_obj:
|
||||||
raise AttributeError
|
raise AttributeError
|
||||||
_GA(self, "_attribute_cache")[attribute_name] = attrib_obj[0] #query is first evaluated here
|
_ATTRIBUTE_CACHE[self][attribute_name] = attrib_obj[0] #query is first evaluated here
|
||||||
return _GA(self, "_attribute_cache")[attribute_name].value
|
return _ATTRIBUTE_CACHE[self][attribute_name].value
|
||||||
return attrib_obj.value
|
return attrib_obj.value
|
||||||
|
|
||||||
def del_attribute(self, attribute_name):
|
def del_attribute(self, attribute_name):
|
||||||
|
|
@ -1296,9 +1303,9 @@ class TypedObject(SharedMemoryModel):
|
||||||
|
|
||||||
attribute_name: (str) The attribute's name.
|
attribute_name: (str) The attribute's name.
|
||||||
"""
|
"""
|
||||||
attr_obj = _GA(self, "_attribute_cache").get(attribute_name)
|
attr_obj = _ATTRIBUTE_CACHE[self].get(attribute_name)
|
||||||
if attr_obj:
|
if attr_obj:
|
||||||
del _GA(self, "_attribute_cache")[attribute_name]
|
del _ATTRIBUTE_CACHE[self][attribute_name]
|
||||||
attr_obj.delete()
|
attr_obj.delete()
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
|
|
@ -1314,9 +1321,9 @@ class TypedObject(SharedMemoryModel):
|
||||||
|
|
||||||
attribute_name: (str) The attribute's name.
|
attribute_name: (str) The attribute's name.
|
||||||
"""
|
"""
|
||||||
attr_obj = _GA(self, "_attribute_cache").get(attribute_name)
|
attr_obj = _ATTRIBUTE_CACHE[self].get(attribute_name)
|
||||||
if attr_obj:
|
if attr_obj:
|
||||||
del _GA(self, "_attribute_cache")[attribute_name]
|
del _ATTRIBUTE_CACHE[self][attribute_name]
|
||||||
attr_obj.delete()
|
attr_obj.delete()
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
"""
|
"""
|
||||||
These are actions for the dummy client runner, using
|
These are actions for the dummy client runner, using
|
||||||
the default command set and intended for unmodified Evennia.
|
the default command set and intended for unmodified Evennia.
|
||||||
|
|
||||||
Each client action is defined as a function. The clients
|
Each client action is defined as a function. The clients
|
||||||
will perform these actions randomly (except the login action).
|
will perform these actions randomly (except the login action).
|
||||||
|
|
||||||
Each action-definition function should take one argument- "client",
|
Each action-definition function should take one argument- "client",
|
||||||
which is a reference to the client currently performing the action
|
which is a reference to the client currently performing the action
|
||||||
Use the client object for saving data between actions.
|
Use the client object for saving data between actions.
|
||||||
|
|
||||||
The client object has the following relevant properties and methods:
|
The client object has the following relevant properties and methods:
|
||||||
cid - unique client id
|
cid - unique client id
|
||||||
istep - the current step
|
istep - the current step
|
||||||
exits - an empty list. Can be used to store exit names
|
exits - an empty list. Can be used to store exit names
|
||||||
objs - an empty list. Can be used to store object names
|
objs - an empty list. Can be used to store object names
|
||||||
counter() - get an integer value. This counts up for every call and
|
counter() - get an integer value. This counts up for every call and
|
||||||
|
|
@ -19,7 +19,7 @@ The client object has the following relevant properties and methods:
|
||||||
|
|
||||||
The action-definition function should return the command that the
|
The action-definition function should return the command that the
|
||||||
client should send to the server (as if it was input in a mud client).
|
client should send to the server (as if it was input in a mud client).
|
||||||
It should also return a string detailing the action taken. This string is
|
It should also return a string detailing the action taken. This string is
|
||||||
used by the "brief verbose" mode of the runner and is prepended by
|
used by the "brief verbose" mode of the runner and is prepended by
|
||||||
"Client N " to produce output like "Client 3 is creating objects ..."
|
"Client N " to produce output like "Client 3 is creating objects ..."
|
||||||
|
|
||||||
|
|
@ -30,10 +30,10 @@ are 2-tuples (probability, action_func), where probability defines how
|
||||||
common it is for that particular action to happen. The runner will
|
common it is for that particular action to happen. The runner will
|
||||||
randomly pick between those functions based on the probability.
|
randomly pick between those functions based on the probability.
|
||||||
|
|
||||||
ACTIONS = (login_func, (0.3, func1), (0.1, func2) ... )
|
ACTIONS = (login_func, (0.3, func1), (0.1, func2) ... )
|
||||||
|
|
||||||
To change the runner to use your custom ACTION and/or action
|
To change the runner to use your custom ACTION and/or action
|
||||||
definitions, edit settings.py and add
|
definitions, edit settings.py and add
|
||||||
|
|
||||||
DUMMYRUNNER_ACTIONS_MODULE = "path.to.your.module"
|
DUMMYRUNNER_ACTIONS_MODULE = "path.to.your.module"
|
||||||
|
|
||||||
|
|
@ -45,27 +45,27 @@ definitions, edit settings.py and add
|
||||||
import time
|
import time
|
||||||
RUNID = time.time()
|
RUNID = time.time()
|
||||||
|
|
||||||
# some convenient templates
|
# some convenient templates
|
||||||
|
|
||||||
START_ROOM = "testing_room_start-%s-%s" % (RUNID, "%i")
|
START_ROOM = "testing_room_start-%s-%s" % (RUNID, "%i")
|
||||||
ROOM_TEMPLATE = "testing_room_%s-%s" % (RUNID, "%i")
|
ROOM_TEMPLATE = "testing_room_%s-%s" % (RUNID, "%i")
|
||||||
EXIT_TEMPLATE = "exit_%s-%s" % (RUNID, "%i")
|
EXIT_TEMPLATE = "exit_%s-%s" % (RUNID, "%i")
|
||||||
OBJ_TEMPLATE = "testing_obj_%s-%s" % (RUNID, "%i")
|
OBJ_TEMPLATE = "testing_obj_%s-%s" % (RUNID, "%i")
|
||||||
TOBJ_TEMPLATE = "testing_button_%s-%s" % (RUNID, "%i")
|
TOBJ_TEMPLATE = "testing_button_%s-%s" % (RUNID, "%i")
|
||||||
TOBJ_TYPECLASS = "examples.red_button.RedButton"
|
TOBJ_TYPECLASS = "examples.red_button.RedButton"
|
||||||
|
|
||||||
# action function definitions
|
# action function definitions
|
||||||
|
|
||||||
def c_login(client):
|
def c_login(client):
|
||||||
"logins to the game"
|
"logins to the game"
|
||||||
cname = "Dummy-%s-%i" % (RUNID, client.cid)
|
cname = "Dummy-%s-%i" % (RUNID, client.cid)
|
||||||
cemail = "%s@dummy.com" % (cname.lower())
|
cemail = "%s@dummy.com" % (cname.lower())
|
||||||
cpwd = "%s-%s" % (RUNID, client.cid)
|
cpwd = "%s-%s" % (RUNID, client.cid)
|
||||||
cmd = ('create "%s" %s %s' % (cname, cemail, cpwd),
|
cmd = ('create "%s" %s %s' % (cname, cemail, cpwd),
|
||||||
'connect %s %s' % (cemail, cpwd),
|
'connect %s %s' % (cemail, cpwd),
|
||||||
'@dig %s' % START_ROOM % client.cid,
|
'@dig %s' % START_ROOM % client.cid,
|
||||||
'@teleport %s' % START_ROOM % client.cid)
|
'@teleport %s' % START_ROOM % client.cid)
|
||||||
|
|
||||||
return cmd, "logs in as %s ..." % cname
|
return cmd, "logs in as %s ..." % cname
|
||||||
|
|
||||||
def c_logout(client):
|
def c_logout(client):
|
||||||
|
|
@ -75,18 +75,18 @@ def c_logout(client):
|
||||||
def c_looks(client):
|
def c_looks(client):
|
||||||
"looks at various objects"
|
"looks at various objects"
|
||||||
cmd = ["look %s" % obj for obj in client.objs]
|
cmd = ["look %s" % obj for obj in client.objs]
|
||||||
if not cmd:
|
if not cmd:
|
||||||
cmd = ["look %s" % exi for exi in client.exits]
|
cmd = ["look %s" % exi for exi in client.exits]
|
||||||
if not cmd:
|
if not cmd:
|
||||||
cmd = "look"
|
cmd = "look"
|
||||||
return cmd, "looks ..."
|
return cmd, "looks ..."
|
||||||
|
|
||||||
def c_examines(client):
|
def c_examines(client):
|
||||||
"examines various objects"
|
"examines various objects"
|
||||||
cmd = ["examine %s" % obj for obj in client.objs]
|
cmd = ["examine %s" % obj for obj in client.objs]
|
||||||
if not cmd:
|
if not cmd:
|
||||||
cmd = ["examine %s" % exi for exi in client.exits]
|
cmd = ["examine %s" % exi for exi in client.exits]
|
||||||
if not cmd:
|
if not cmd:
|
||||||
cmd = "examine me"
|
cmd = "examine me"
|
||||||
return cmd, "examines objs ..."
|
return cmd, "examines objs ..."
|
||||||
|
|
||||||
|
|
@ -96,30 +96,30 @@ def c_help(client):
|
||||||
'help @teleport',
|
'help @teleport',
|
||||||
'help look',
|
'help look',
|
||||||
'help @tunnel',
|
'help @tunnel',
|
||||||
'help @dig')
|
'help @dig')
|
||||||
return cmd, "reads help ..."
|
return cmd, "reads help ..."
|
||||||
|
|
||||||
def c_digs(client):
|
def c_digs(client):
|
||||||
"digs a new room, storing exit names on client"
|
"digs a new room, storing exit names on client"
|
||||||
roomname = ROOM_TEMPLATE % client.counter()
|
roomname = ROOM_TEMPLATE % client.counter()
|
||||||
exitname1 = EXIT_TEMPLATE % client.counter()
|
exitname1 = EXIT_TEMPLATE % client.counter()
|
||||||
exitname2 = EXIT_TEMPLATE % client.counter()
|
exitname2 = EXIT_TEMPLATE % client.counter()
|
||||||
client.exits.extend([exitname1, exitname2])
|
client.exits.extend([exitname1, exitname2])
|
||||||
cmd = '@dig %s = %s, %s' % (roomname, exitname1, exitname2)
|
cmd = '@dig %s = %s, %s' % (roomname, exitname1, exitname2)
|
||||||
return cmd, "digs ..."
|
return cmd, "digs ..."
|
||||||
|
|
||||||
def c_creates_obj(client):
|
def c_creates_obj(client):
|
||||||
"creates normal objects, storing their name on client"
|
"creates normal objects, storing their name on client"
|
||||||
objname = OBJ_TEMPLATE % client.counter()
|
objname = OBJ_TEMPLATE % client.counter()
|
||||||
client.objs.append(objname)
|
client.objs.append(objname)
|
||||||
cmd = ('@create %s' % objname,
|
cmd = ('@create %s' % objname,
|
||||||
'@desc %s = "this is a test object' % objname,
|
'@desc %s = "this is a test object' % objname,
|
||||||
'@set %s/testattr = this is a test attribute value.' % objname,
|
'@set %s/testattr = this is a test attribute value.' % objname,
|
||||||
'@set %s/testattr2 = this is a second test attribute.' % objname)
|
'@set %s/testattr2 = this is a second test attribute.' % objname)
|
||||||
return cmd, "creates obj ..."
|
return cmd, "creates obj ..."
|
||||||
|
|
||||||
def c_creates_button(client):
|
def c_creates_button(client):
|
||||||
"creates example button, storing name on client"
|
"creates example button, storing name on client"
|
||||||
objname = TOBJ_TEMPLATE % client.counter()
|
objname = TOBJ_TEMPLATE % client.counter()
|
||||||
client.objs.append(objname)
|
client.objs.append(objname)
|
||||||
cmd = ('@create %s:%s' % (objname, TOBJ_TYPECLASS),
|
cmd = ('@create %s:%s' % (objname, TOBJ_TYPECLASS),
|
||||||
|
|
@ -134,21 +134,43 @@ def c_moves(client):
|
||||||
|
|
||||||
|
|
||||||
# Action tuple (required)
|
# Action tuple (required)
|
||||||
#
|
#
|
||||||
# This is a tuple of client action functions. The first element is the
|
# This is a tuple of client action functions. The first element is the
|
||||||
# function the client should use to log into the game and move to
|
# function the client should use to log into the game and move to
|
||||||
# STARTROOM . The second element is the logout command, for cleanly
|
# STARTROOM . The second element is the logout command, for cleanly
|
||||||
# exiting the mud. The following elements are 2-tuples of (probability,
|
# exiting the mud. The following elements are 2-tuples of (probability,
|
||||||
# action_function). The probablities should normally sum up to 1,
|
# action_function). The probablities should normally sum up to 1,
|
||||||
# otherwise the system will normalize them.
|
# otherwise the system will normalize them.
|
||||||
#
|
#
|
||||||
|
|
||||||
ACTIONS = ( c_login,
|
# heavy builder definition
|
||||||
c_logout,
|
#ACTIONS = ( c_login,
|
||||||
(0.2, c_looks),
|
# c_logout,
|
||||||
(0.1, c_examines),
|
# (0.2, c_looks),
|
||||||
(0.2, c_help),
|
# (0.1, c_examines),
|
||||||
(0.1, c_digs),
|
# (0.2, c_help),
|
||||||
(0.1, c_creates_obj),
|
# (0.1, c_digs),
|
||||||
|
# (0.1, c_creates_obj),
|
||||||
|
# #(0.1, c_creates_button),
|
||||||
|
# (0.2, c_moves))
|
||||||
|
# "normal builder" definition
|
||||||
|
ACTIONS = ( c_login,
|
||||||
|
c_logout,
|
||||||
|
(0.5, c_looks),
|
||||||
|
(0.08, c_examines),
|
||||||
|
(0.1, c_help),
|
||||||
|
(0.01, c_digs),
|
||||||
|
(0.01, c_creates_obj),
|
||||||
#(0.1, c_creates_button),
|
#(0.1, c_creates_button),
|
||||||
(0.2, c_moves))
|
(0.3, c_moves))
|
||||||
|
# "normal player" definition
|
||||||
|
#ACTIONS = ( c_login,
|
||||||
|
# c_logout,
|
||||||
|
# (0.4, c_looks),
|
||||||
|
# #(0.1, c_examines),
|
||||||
|
# (0.2, c_help),
|
||||||
|
# #(0.1, c_digs),
|
||||||
|
# #(0.1, c_creates_obj),
|
||||||
|
# #(0.1, c_creates_button),
|
||||||
|
# (0.4, c_moves))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ be of use when designing your own game.
|
||||||
"""
|
"""
|
||||||
from inspect import ismodule
|
from inspect import ismodule
|
||||||
import os, sys, imp, types, math
|
import os, sys, imp, types, math
|
||||||
from collections import Counter
|
|
||||||
import textwrap
|
import textwrap
|
||||||
import datetime
|
import datetime
|
||||||
import random
|
import random
|
||||||
|
|
@ -460,7 +459,14 @@ def run_async(async_func, at_return=None, at_err=None):
|
||||||
Use this function with restrain and only for features/commands
|
Use this function with restrain and only for features/commands
|
||||||
that you know has no influence on the cause-and-effect order of your
|
that you know has no influence on the cause-and-effect order of your
|
||||||
game (commands given after the async function might be executed before
|
game (commands given after the async function might be executed before
|
||||||
it has finished).
|
it has finished). Accessing the same property from different threads can
|
||||||
|
lead to unpredicted behaviour if you are not careful (this is called a
|
||||||
|
"race condition").
|
||||||
|
|
||||||
|
Also note that some databases, notably sqlite3, don't support access from
|
||||||
|
multiple threads simultaneously, so if you do heavy database access from
|
||||||
|
your async_func under sqlite3 you will probably run very slow or even get
|
||||||
|
tracebacks.
|
||||||
|
|
||||||
async_func() - function that should be run asynchroneously
|
async_func() - function that should be run asynchroneously
|
||||||
at_return(r) - if given, this function will be called when async_func returns
|
at_return(r) - if given, this function will be called when async_func returns
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue