mirror of
https://github.com/evennia/evennia.git
synced 2026-03-16 21:06:30 +01:00
Refactored the devel/bin structure to hopefully make the system work better on Windows.
This commit is contained in:
parent
6323ba965b
commit
f780f469b1
12 changed files with 32 additions and 21 deletions
905
bin/evennia
905
bin/evennia
|
|
@ -1,905 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
"""
|
||||
EVENNIA SERVER STARTUP SCRIPT
|
||||
|
||||
This is the start point for running Evennia.
|
||||
|
||||
Sets the appropriate environmental variables and launches the server
|
||||
and portal through the evennia_runner. Run without arguments to get a
|
||||
menu. Run the script with the -h flag to see usage information.
|
||||
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import signal
|
||||
import shutil
|
||||
import importlib
|
||||
from argparse import ArgumentParser
|
||||
from subprocess import Popen, check_output, call, CalledProcessError, STDOUT
|
||||
import django
|
||||
|
||||
# Signal processing
|
||||
SIG = signal.SIGINT
|
||||
|
||||
# Set up the main python paths to Evennia
|
||||
EVENNIA_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
EVENNIA_BIN = os.path.join(EVENNIA_ROOT, "bin")
|
||||
|
||||
import evennia
|
||||
EVENNIA_LIB = os.path.join(os.path.dirname(os.path.abspath(evennia.__file__)))
|
||||
EVENNIA_RUNNER = os.path.join(EVENNIA_BIN, "evennia_runner.py")
|
||||
EVENNIA_TEMPLATE = os.path.join(EVENNIA_LIB, "game_template")
|
||||
EVENNIA_BINTESTING = os.path.join(EVENNIA_BIN, "testing")
|
||||
EVENNIA_DUMMYRUNNER = os.path.join(EVENNIA_BINTESTING, "dummyrunner.py")
|
||||
|
||||
TWISTED_BINARY = "twistd"
|
||||
|
||||
# Game directory structure
|
||||
SETTINGFILE = "settings.py"
|
||||
SERVERDIR = "server"
|
||||
CONFDIR = os.path.join(SERVERDIR, "conf")
|
||||
SETTINGS_PATH = os.path.join(CONFDIR, SETTINGFILE)
|
||||
SETTINGS_DOTPATH = "server.conf.settings"
|
||||
CURRENT_DIR = os.getcwd()
|
||||
GAMEDIR = CURRENT_DIR
|
||||
|
||||
# Operational setup
|
||||
SERVER_LOGFILE = None
|
||||
PORTAL_LOGFILE = None
|
||||
HTTP_LOGFILE = None
|
||||
SERVER_PIDFILE = None
|
||||
PORTAL_PIDFILE = None
|
||||
SERVER_RESTART = None
|
||||
PORTAL_RESTART = None
|
||||
SERVER_PY_FILE = None
|
||||
PORTAL_PY_FILE = None
|
||||
|
||||
PYTHON_MIN = '2.7'
|
||||
TWISTED_MIN = '12.0'
|
||||
DJANGO_MIN = '1.7'
|
||||
DJANGO_REC = '1.7'
|
||||
|
||||
# add Evennia root to PYTHONPATH note that bin/evennia.py is
|
||||
# automatically added to sys.modules and will be imported first, which
|
||||
# is not what we want. So we remove it manually and set the path so
|
||||
# the root/evennia package is found first instead.
|
||||
del sys.modules["evennia"]
|
||||
sys.path[0] = EVENNIA_ROOT
|
||||
|
||||
#------------------------------------------------------------
|
||||
#
|
||||
# Messages
|
||||
#
|
||||
#------------------------------------------------------------
|
||||
|
||||
CREATED_NEW_GAMEDIR = \
|
||||
"""
|
||||
Welcome to Evennia!
|
||||
Created a new Evennia game directory '{gamedir}'.
|
||||
|
||||
You can now optionally edit your new settings file
|
||||
at {settings_path}. If you don't, the defaults
|
||||
will work out of the box. When ready to continue, 'cd' to your
|
||||
game directory and run:
|
||||
|
||||
evennia migrate
|
||||
|
||||
This initializes the database. To start the server for the first
|
||||
time, run:
|
||||
|
||||
evennia -i start
|
||||
|
||||
Make sure to create a superuser when asked for it. You should now
|
||||
be able to (by default) connect to your server on server
|
||||
'localhost', port 4000 using a telnet/mud client or
|
||||
http://localhost:8000 using your web browser. If things don't
|
||||
work, check so those ports are open.
|
||||
|
||||
"""
|
||||
|
||||
ERROR_NO_GAMEDIR = \
|
||||
"""
|
||||
No Evennia settings file was found. You must run this command from
|
||||
inside a valid game directory first created with --init.
|
||||
"""
|
||||
|
||||
WARNING_RUNSERVER = \
|
||||
"""
|
||||
WARNING: There is no need to run the Django development
|
||||
webserver to test out Evennia web features (the web client
|
||||
will in fact not work since the Django test server knows
|
||||
nothing about MUDs). Instead, just start Evennia with the
|
||||
webserver component active (this is the default).
|
||||
"""
|
||||
|
||||
ERROR_SETTINGS = \
|
||||
"""
|
||||
There was an error importing Evennia's config file {settingspath}. There is usually
|
||||
one of three reasons for this:
|
||||
1) You are not running this command from your game directory.
|
||||
Change directory to your game directory and try again (or
|
||||
create a new game directory using evennia --init <dirname>)
|
||||
2) The settings file contains a syntax error. If you see a
|
||||
traceback above, review it, resolve the problem and try again.
|
||||
3) Django is not correctly installed. This usually shows as
|
||||
errors mentioning 'DJANGO_SETTINGS_MODULE'. If you run a
|
||||
virtual machine, it might be worth to restart it to see if
|
||||
this resolves the issue.
|
||||
""".format(settingsfile=SETTINGFILE, settingspath=SETTINGS_PATH)
|
||||
|
||||
ERROR_DATABASE = \
|
||||
"""
|
||||
Your database does not seem to be set up correctly.
|
||||
(error was '{traceback}')
|
||||
|
||||
Standing in your game directory, try to run
|
||||
|
||||
evennia migrate
|
||||
|
||||
to initialize/update the database according to your settings.
|
||||
"""
|
||||
|
||||
ERROR_WINDOWS_WIN32API = \
|
||||
"""
|
||||
ERROR: Unable to import win32api, which Twisted requires to run.
|
||||
You may download it from:
|
||||
|
||||
http://sourceforge.net/projects/pywin32
|
||||
or
|
||||
http://starship.python.net/crew/mhammond/win32/Downloads.html
|
||||
"""
|
||||
|
||||
INFO_WINDOWS_BATFILE = \
|
||||
"""
|
||||
INFO: Since you are running Windows, a file 'twistd.bat' was
|
||||
created for you. This is a simple batch file that tries to call
|
||||
the twisted executable. Evennia determined this to be:
|
||||
|
||||
%(twistd_path)s
|
||||
|
||||
If you run into errors at startup you might need to edit
|
||||
twistd.bat to point to the actual location of the Twisted
|
||||
executable (usually called twistd.py) on your machine.
|
||||
|
||||
This procedure is only done once. Run evennia.py again when you
|
||||
are ready to start the server.
|
||||
"""
|
||||
|
||||
CMDLINE_HELP = \
|
||||
"""
|
||||
Starts or operates the Evennia MU* server. Also allows for
|
||||
initializing a new game directory and managing the game's
|
||||
database. You can also pass django manage.py arguments through
|
||||
this launcher. If you need manage.py --options, use djangoadmin
|
||||
directly instead.
|
||||
"""
|
||||
|
||||
|
||||
VERSION_INFO = \
|
||||
"""
|
||||
{about}
|
||||
Evennia {version}
|
||||
OS: {os}
|
||||
Python: {python}
|
||||
Twisted: {twisted}
|
||||
Django: {django}
|
||||
"""
|
||||
|
||||
ABOUT_INFO= \
|
||||
"""
|
||||
Evennia MUD/MUX/MU* development system
|
||||
|
||||
Licence: BSD 3-Clause Licence
|
||||
Web: http://www.evennia.com
|
||||
Irc: #evennia on FreeNode
|
||||
Forum: http://www.evennia.com/discussions
|
||||
Maintainer (2010-): Griatch (griatch AT gmail DOT com)
|
||||
Maintainer (2006-10): Greg Taylor
|
||||
|
||||
Use -h for command line options.
|
||||
"""
|
||||
|
||||
HELP_ENTRY = \
|
||||
"""
|
||||
Enter 'evennia -h' for command-line options.
|
||||
|
||||
Use option (1) in a production environment. During development (2) is
|
||||
usually enough, portal debugging is usually only useful if you are
|
||||
adding new protocols or are debugging Evennia itself.
|
||||
|
||||
Reload with (5) to update the server with your changes without
|
||||
disconnecting any players.
|
||||
|
||||
Note: Reload and stop are sometimes poorly supported in Windows. If you have
|
||||
issues, log into the game to stop or restart the server instead.
|
||||
"""
|
||||
|
||||
MENU = \
|
||||
"""
|
||||
+----Evennia Launcher-------------------------------------------+
|
||||
| |
|
||||
+--- Starting --------------------------------------------------+
|
||||
| |
|
||||
| 1) (normal): All output to logfiles |
|
||||
| 2) (server devel): Server logs to terminal (-i option) |
|
||||
| 3) (portal devel): Portal logs to terminal |
|
||||
| 4) (full devel): Both Server and Portal logs to terminal |
|
||||
| |
|
||||
+--- Restarting ------------------------------------------------+
|
||||
| |
|
||||
| 5) Reload the Server |
|
||||
| 6) Reload the Portal (only works with portal/full debug) |
|
||||
| |
|
||||
+--- Stopping --------------------------------------------------+
|
||||
| |
|
||||
| 7) Stopping both Portal and Server |
|
||||
| 8) Stopping only Server |
|
||||
| 9) Stopping only Portal |
|
||||
| |
|
||||
+---------------------------------------------------------------+
|
||||
| h) Help i) About info q) Abort |
|
||||
+---------------------------------------------------------------+
|
||||
"""
|
||||
|
||||
ERROR_PYTHON_VERSION = \
|
||||
"""
|
||||
ERROR: Python {pversion} used. Evennia requires version
|
||||
{python_min} or higher (but not 3.x).
|
||||
"""
|
||||
|
||||
WARNING_TWISTED_VERSION = \
|
||||
"""
|
||||
WARNING: Twisted {tversion} found. Evennia recommends
|
||||
v{twisted_min} or higher."
|
||||
"""
|
||||
|
||||
ERROR_NOTWISTED = \
|
||||
"""
|
||||
ERROR: Twisted does not seem to be installed.
|
||||
"""
|
||||
|
||||
ERROR_DJANGO_MIN = \
|
||||
"""
|
||||
ERROR: Django {dversion} found. Evennia requires version
|
||||
{django_min} or higher.
|
||||
"""
|
||||
|
||||
NOTE_DJANGO_MIN = \
|
||||
"""
|
||||
NOTE: Django {dversion} found. This will work, but v{django_rec}
|
||||
is recommended for production.
|
||||
"""
|
||||
|
||||
NOTE_DJANGO_NEW = \
|
||||
"""
|
||||
NOTE: Django {dversion} found. This is newer than Evennia's
|
||||
recommended version (v{django_rec}). It will probably work, but
|
||||
may be new enough not to be fully tested yet. Report any issues."
|
||||
"""
|
||||
|
||||
ERROR_NODJANGO = \
|
||||
"""
|
||||
ERROR: Django does not seem to be installed.
|
||||
"""
|
||||
|
||||
#------------------------------------------------------------
|
||||
#
|
||||
# Functions
|
||||
#
|
||||
#------------------------------------------------------------
|
||||
|
||||
def evennia_version():
|
||||
"""
|
||||
Get the Evennia version info from the main package.
|
||||
"""
|
||||
version = "Unknown"
|
||||
try:
|
||||
import evennia
|
||||
version = evennia.__version__
|
||||
except ImportError:
|
||||
pass
|
||||
try:
|
||||
version = "%s (rev %s)" % (version, check_output("git rev-parse --short HEAD", shell=True, cwd=EVENNIA_ROOT, stderr=STDOUT).strip())
|
||||
except (IOError, CalledProcessError):
|
||||
pass
|
||||
return version
|
||||
|
||||
EVENNIA_VERSION = evennia_version()
|
||||
|
||||
|
||||
def check_main_evennia_dependencies():
|
||||
"""
|
||||
Checks and imports the Evennia dependencies. This must be done
|
||||
already before the paths are set up.
|
||||
"""
|
||||
error = False
|
||||
|
||||
# Python
|
||||
pversion = ".".join(str(num) for num in sys.version_info if type(num) == int)
|
||||
if pversion < PYTHON_MIN:
|
||||
print ERROR_PYTHON_VERSION.format(pversion=pversion, python_min=PYTHON_MIN)
|
||||
error = True
|
||||
# Twisted
|
||||
try:
|
||||
import twisted
|
||||
tversion = twisted.version.short()
|
||||
if tversion < TWISTED_MIN:
|
||||
print WARNING_TWISTED_VERSION.format(tversion=tversion, twisted_min=TWISTED_MIN)
|
||||
except ImportError:
|
||||
print ERROR_NOTWISTED
|
||||
error = True
|
||||
# Django
|
||||
try:
|
||||
dversion = ".".join(str(num) for num in django.VERSION if type(num) == int)
|
||||
# only the main version (1.5, not 1.5.4.0)
|
||||
dversion_main = ".".join(dversion.split(".")[:2])
|
||||
if dversion < DJANGO_MIN:
|
||||
print ERROR_DJANGO_MIN.format(dversion=dversion_main, django_min=DJANGO_MIN)
|
||||
error = True
|
||||
elif DJANGO_MIN <= dversion < DJANGO_REC:
|
||||
print NOTE_DJANGO_MIN.format(dversion=dversion_main, django_rec=DJANGO_REC)
|
||||
elif DJANGO_REC < dversion_main:
|
||||
print NOTE_DJANGO_NEW.format(dversion=dversion_main, django_rec=DJANGO_REC)
|
||||
except ImportError:
|
||||
print ERROR_NODJANGO
|
||||
error = True
|
||||
if error:
|
||||
sys.exit()
|
||||
|
||||
|
||||
def set_gamedir(path):
|
||||
"""
|
||||
Set GAMEDIR based on path, by figuring out where the setting file
|
||||
is inside the directory tree.
|
||||
"""
|
||||
|
||||
global GAMEDIR
|
||||
if os.path.exists(os.path.join(path, SETTINGS_PATH)):
|
||||
# path at root of game dir
|
||||
GAMEDIR = os.path.abspath(path)
|
||||
elif os.path.exists(os.path.join(path, os.path.pardir, SETTINGS_PATH)):
|
||||
# path given to somewhere one level down
|
||||
GAMEDIR = os.path.dirname(path)
|
||||
elif os.path.exists(os.path.join(path, os.path.pardir, os.path.pardir, SETTINGS_PATH)):
|
||||
# path given to somwhere two levels down
|
||||
GAMEDIR = os.path.dirname(os.path.dirname(path))
|
||||
elif os.path.exists(os.path.join(path, os.path.pardir, os.path. pardir, os.path.pardir, SETTINGS_PATH)):
|
||||
# path given to somewhere three levels down (custom directories)
|
||||
GAMEDIR = os.path.dirname(os.path.dirname(os.path.dirname(path)))
|
||||
else:
|
||||
# we don't look further down than this ...
|
||||
print ERROR_NO_GAMEDIR
|
||||
sys.exit()
|
||||
|
||||
|
||||
def create_secret_key():
|
||||
"""
|
||||
Randomly create the secret key for the settings file
|
||||
"""
|
||||
import random
|
||||
import string
|
||||
secret_key = list((string.letters +
|
||||
string.digits + string.punctuation).replace("\\", "").replace("'", '"'))
|
||||
random.shuffle(secret_key)
|
||||
secret_key = "".join(secret_key[:40])
|
||||
return secret_key
|
||||
|
||||
|
||||
def create_settings_file():
|
||||
"""
|
||||
Uses the template settings file to build a working
|
||||
settings file.
|
||||
"""
|
||||
settings_path = os.path.join(GAMEDIR, "server", "conf", "settings.py")
|
||||
with open(settings_path, 'r') as f:
|
||||
settings_string = f.read()
|
||||
|
||||
# tweak the settings
|
||||
setting_dict = {"settings_default": os.path.join(EVENNIA_LIB, "settings_default.py"),
|
||||
"servername":"\"%s\"" % GAMEDIR.rsplit(os.path.sep, 1)[1].capitalize(),
|
||||
"game_dir":"\"%s\"" % GAMEDIR,
|
||||
"secret_key":"\'%s\'" % create_secret_key()}
|
||||
|
||||
# modify the settings
|
||||
settings_string = settings_string.format(**setting_dict)
|
||||
|
||||
with open(settings_path, 'w') as f:
|
||||
f.write(settings_string)
|
||||
|
||||
|
||||
def create_game_directory(dirname):
|
||||
"""
|
||||
Initialize a new game directory named dirname
|
||||
at the current path. This means copying the
|
||||
template directory from evennia's root.
|
||||
"""
|
||||
global GAMEDIR
|
||||
GAMEDIR = os.path.abspath(os.path.join(CURRENT_DIR, dirname))
|
||||
if os.path.exists(GAMEDIR):
|
||||
print "Cannot create new Evennia game dir: '%s' already exists." % dirname
|
||||
sys.exit()
|
||||
# copy template directory
|
||||
shutil.copytree(EVENNIA_TEMPLATE, GAMEDIR)
|
||||
# pre-build settings file in the new GAMEDIR
|
||||
create_settings_file()
|
||||
|
||||
|
||||
def create_superuser():
|
||||
"Create the superuser player"
|
||||
print "\nCreate a superuser below. The superuser is Player #1, the 'owner' account of the server.\n"
|
||||
django.core.management.call_command("createsuperuser", interactive=True)
|
||||
|
||||
|
||||
def check_database(exit_on_error=False):
|
||||
"""
|
||||
Check database exists
|
||||
"""
|
||||
# Check so a database exists and is accessible
|
||||
from django.db import connection
|
||||
tables = connection.introspection.get_table_list(connection.cursor())
|
||||
if tables and u'players_playerdb' in tables:
|
||||
# database exists and seems set up. Initialize evennia.
|
||||
import evennia
|
||||
evennia.init()
|
||||
else:
|
||||
if exit_on_error:
|
||||
print ERROR_DATABASE.format(traceback=e)
|
||||
sys.exit()
|
||||
return False
|
||||
return True
|
||||
# Try to get Player#1
|
||||
from evennia.players.models import PlayerDB
|
||||
try:
|
||||
PlayerDB.objects.get(id=1)
|
||||
except PlayerDB.DoesNotExist:
|
||||
# no superuser yet. We need to create it.
|
||||
create_superuser()
|
||||
return True
|
||||
|
||||
|
||||
def getenv():
|
||||
"""
|
||||
Get current environment and add PYTHONPATH
|
||||
"""
|
||||
sep = ";" if os.name == 'nt' else ":"
|
||||
env = os.environ.copy()
|
||||
env['PYTHONPATH'] = sep.join(sys.path)
|
||||
return env
|
||||
|
||||
|
||||
def get_pid(pidfile):
|
||||
"""
|
||||
Get the PID (Process ID) by trying to access
|
||||
an PID file.
|
||||
"""
|
||||
pid = None
|
||||
if os.path.exists(pidfile):
|
||||
f = open(pidfile, 'r')
|
||||
pid = f.read()
|
||||
return pid
|
||||
|
||||
|
||||
def del_pid(pidfile):
|
||||
"""
|
||||
The pidfile should normally be removed after a process has
|
||||
finished, but when sending certain signals they remain, so we need
|
||||
to clean them manually.
|
||||
"""
|
||||
if os.path.exists(pidfile):
|
||||
os.remove(pidfile)
|
||||
|
||||
|
||||
def kill(pidfile, signal=SIG, succmsg="", errmsg="", restart_file=SERVER_RESTART, restart=False):
|
||||
"""
|
||||
Send a kill signal to a process based on PID. A customized
|
||||
success/error message will be returned. If clean=True, the system
|
||||
will attempt to manually remove the pid file.
|
||||
"""
|
||||
pid = get_pid(pidfile)
|
||||
if pid:
|
||||
if os.name == 'nt':
|
||||
os.remove(pidfile)
|
||||
# set restart/norestart flag
|
||||
if restart:
|
||||
django.core.management.call_command('collectstatic', interactive=False, verbosity=0)
|
||||
with open(restart_file, 'w') as f:
|
||||
f.write("reload")
|
||||
else:
|
||||
with open(restart_file, 'w') as f:
|
||||
f.write("shutdown")
|
||||
try:
|
||||
os.kill(int(pid), signal)
|
||||
except OSError:
|
||||
print "Process %(pid)s cannot be stopped. "\
|
||||
"The PID file 'server/%(pidfile)s' seems stale. "\
|
||||
"Try removing it." % {'pid': pid, 'pidfile': pidfile}
|
||||
return
|
||||
print "Evennia:", succmsg
|
||||
return
|
||||
print "Evennia:", errmsg
|
||||
|
||||
|
||||
def show_version_info(about=False):
|
||||
"""
|
||||
Display version info
|
||||
"""
|
||||
import os, sys
|
||||
import twisted
|
||||
import django
|
||||
|
||||
return VERSION_INFO.format(version=EVENNIA_VERSION,
|
||||
about=ABOUT_INFO if about else "",
|
||||
os=os.name, python=sys.version.split()[0],
|
||||
twisted=twisted.version.short(),
|
||||
django=django.get_version())
|
||||
|
||||
|
||||
def error_check_python_modules():
|
||||
"""
|
||||
Import settings modules in settings. This will raise exceptions on
|
||||
pure python-syntax issues which are hard to catch gracefully
|
||||
with exceptions in the engine (since they are formatting errors in
|
||||
the python source files themselves). Best they fail already here
|
||||
before we get any further.
|
||||
"""
|
||||
from django.conf import settings
|
||||
def imp(path, split=True):
|
||||
mod, fromlist = path, "None"
|
||||
if split:
|
||||
mod, fromlist = path.rsplit('.', 1)
|
||||
__import__(mod, fromlist=[fromlist])
|
||||
|
||||
# core modules
|
||||
imp(settings.COMMAND_PARSER)
|
||||
imp(settings.SEARCH_AT_RESULT)
|
||||
imp(settings.SEARCH_AT_MULTIMATCH_INPUT)
|
||||
imp(settings.CONNECTION_SCREEN_MODULE)
|
||||
#imp(settings.AT_INITIAL_SETUP_HOOK_MODULE, split=False)
|
||||
for path in settings.LOCK_FUNC_MODULES:
|
||||
imp(path, split=False)
|
||||
# cmdsets
|
||||
|
||||
deprstring = "settings.%s should be renamed to %s. If defaults are used, " \
|
||||
"their path/classname must be updated (see evennia/settings_default.py)."
|
||||
if hasattr(settings, "CMDSET_DEFAULT"):
|
||||
raise DeprecationWarning(deprstring % ("CMDSET_DEFAULT", "CMDSET_CHARACTER"))
|
||||
if hasattr(settings, "CMDSET_OOC"):
|
||||
raise DeprecationWarning(deprstring % ("CMDSET_OOC", "CMDSET_PLAYER"))
|
||||
if settings.WEBSERVER_ENABLED and not isinstance(settings.WEBSERVER_PORTS[0], tuple):
|
||||
raise DeprecationWarning("settings.WEBSERVER_PORTS must be on the form [(proxyport, serverport), ...]")
|
||||
if hasattr(settings, "BASE_COMM_TYPECLASS"):
|
||||
raise DeprecationWarning(deprstring % ("BASE_COMM_TYPECLASS", "BASE_CHANNEL_TYPECLASS"))
|
||||
if hasattr(settings, "COMM_TYPECLASS_PATHS"):
|
||||
raise DeprecationWarning(deprstring % ("COMM_TYPECLASS_PATHS", "CHANNEL_TYPECLASS_PATHS"))
|
||||
if hasattr(settings, "CHARACTER_DEFAULT_HOME"):
|
||||
raise DeprecationWarning("settings.CHARACTER_DEFAULT_HOME should be renamed to DEFAULT_HOME. " \
|
||||
"See also settings.START_LOCATION (see evennia/settings_default.py).")
|
||||
|
||||
from evennia.commands import cmdsethandler
|
||||
if not cmdsethandler.import_cmdset(settings.CMDSET_UNLOGGEDIN, None): print "Warning: CMDSET_UNLOGGED failed to load!"
|
||||
if not cmdsethandler.import_cmdset(settings.CMDSET_CHARACTER, None): print "Warning: CMDSET_CHARACTER failed to load"
|
||||
if not cmdsethandler.import_cmdset(settings.CMDSET_PLAYER, None): print "Warning: CMDSET_PLAYER failed to load"
|
||||
# typeclasses
|
||||
imp(settings.BASE_PLAYER_TYPECLASS)
|
||||
imp(settings.BASE_OBJECT_TYPECLASS)
|
||||
imp(settings.BASE_CHARACTER_TYPECLASS)
|
||||
imp(settings.BASE_ROOM_TYPECLASS)
|
||||
imp(settings.BASE_EXIT_TYPECLASS)
|
||||
imp(settings.BASE_SCRIPT_TYPECLASS)
|
||||
|
||||
|
||||
def init_game_directory(path, check_db=True):
|
||||
"""
|
||||
Try to analyze the given path to find settings.py - this defines
|
||||
the game directory and also sets PYTHONPATH as well as the
|
||||
django path.
|
||||
"""
|
||||
# set the GAMEDIR path
|
||||
set_gamedir(path)
|
||||
|
||||
# Add gamedir to python path
|
||||
sys.path.insert(1, GAMEDIR)
|
||||
|
||||
# Prepare django; set the settings location
|
||||
os.environ['DJANGO_SETTINGS_MODULE'] = SETTINGS_DOTPATH
|
||||
|
||||
# required since django1.7
|
||||
django.setup()
|
||||
|
||||
# test existence of the settings module
|
||||
try:
|
||||
from django.conf import settings
|
||||
except Exception, ex:
|
||||
if not str(ex).startswith("No module named"):
|
||||
import traceback
|
||||
print traceback.format_exc().strip()
|
||||
print ERROR_SETTINGS
|
||||
sys.exit()
|
||||
|
||||
# this will both check the database and initialize the evennia dir.
|
||||
if check_db:
|
||||
check_database()
|
||||
|
||||
# set up the Evennia executables and log file locations
|
||||
global SERVER_PY_FILE, PORTAL_PY_FILE
|
||||
global SERVER_LOGFILE, PORTAL_LOGFILE, HTTP_LOGFILE
|
||||
global SERVER_PIDFILE, PORTAL_PIDFILE
|
||||
global SERVER_RESTART, PORTAL_RESTART
|
||||
global EVENNIA_VERSION
|
||||
|
||||
SERVER_PY_FILE = os.path.join(EVENNIA_LIB, "server", "server.py")
|
||||
PORTAL_PY_FILE = os.path.join(EVENNIA_LIB, "portal", "portal", "portal.py")
|
||||
|
||||
SERVER_PIDFILE = os.path.join(GAMEDIR, SERVERDIR, "server.pid")
|
||||
PORTAL_PIDFILE = os.path.join(GAMEDIR, SERVERDIR, "portal.pid")
|
||||
|
||||
SERVER_RESTART = os.path.join(GAMEDIR, SERVERDIR, "server.restart")
|
||||
PORTAL_RESTART = os.path.join(GAMEDIR, SERVERDIR, "portal.restart")
|
||||
|
||||
SERVER_LOGFILE = settings.SERVER_LOG_FILE
|
||||
PORTAL_LOGFILE = settings.PORTAL_LOG_FILE
|
||||
HTTP_LOGFILE = settings.HTTP_LOG_FILE
|
||||
|
||||
if os.name == 'nt':
|
||||
# We need to handle Windows twisted separately. We create a
|
||||
# batchfile in game/server, linking to the actual binary
|
||||
|
||||
global TWISTED_BINARY
|
||||
TWISTED_BINARY = "twistd.bat"
|
||||
|
||||
# add path so system can find the batfile
|
||||
sys.path.insert(1, os.path.join(GAMEDIR, SERVERDIR))
|
||||
|
||||
try:
|
||||
importlib.import_module("win32api")
|
||||
except ImportError:
|
||||
print ERROR_WINDOWS_WIN32API
|
||||
sys.exit()
|
||||
|
||||
if not os.path.exists(os.path.join(EVENNIA_BIN, TWISTED_BINARY)):
|
||||
# Test for executable twisted batch file. This calls the
|
||||
# twistd.py executable that is usually not found on the
|
||||
# path in Windows. It's not enough to locate
|
||||
# scripts.twistd, what we want is the executable script
|
||||
# C:\PythonXX/Scripts/twistd.py. Alas we cannot hardcode
|
||||
# this location since we don't know if user has Python in
|
||||
# a non-standard location. So we try to figure it out.
|
||||
twistd = importlib.import_module("twisted.scripts.twistd")
|
||||
twistd_dir = os.path.dirname(twistd.__file__)
|
||||
|
||||
# note that we hope the twistd package won't change here, since we
|
||||
# try to get to the executable by relative path.
|
||||
twistd_path = os.path.abspath(os.path.join(twistd_dir,
|
||||
os.pardir, os.pardir, os.pardir, os.pardir,
|
||||
'scripts', 'twistd.py'))
|
||||
|
||||
with open('twistd.bat', 'w') as bat_file:
|
||||
# build a custom bat file for windows
|
||||
bat_file.write("@\"%s\" \"%s\" %%*" % (sys.executable, twistd_path))
|
||||
|
||||
print INFO_WINDOWS_BATFILE.format(twistd_path=twistd_path)
|
||||
|
||||
def run_dummyrunner(number_of_dummies):
|
||||
"""
|
||||
Start an instance of the dummyrunner
|
||||
|
||||
The dummy players' behavior can be customized by adding a
|
||||
dummyrunner_settings.py config file in the game's conf directory.
|
||||
"""
|
||||
number_of_dummies = str(int(number_of_dummies)) if number_of_dummies else 1
|
||||
cmdstr = [sys.executable, EVENNIA_DUMMYRUNNER, "-N", number_of_dummies]
|
||||
config_file = os.path.join(SETTINGS_PATH, "dummyrunner_settings.py")
|
||||
if os.path.exists(config_file):
|
||||
cmdstr.extend(["--config", config_file])
|
||||
try:
|
||||
call(cmdstr, env=getenv())
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
|
||||
def run_menu():
|
||||
"""
|
||||
This launches an interactive menu.
|
||||
"""
|
||||
while True:
|
||||
# menu loop
|
||||
|
||||
print MENU
|
||||
inp = raw_input(" option > ")
|
||||
|
||||
# quitting and help
|
||||
if inp.lower() == 'q':
|
||||
return
|
||||
elif inp.lower() == 'h':
|
||||
print HELP_ENTRY
|
||||
raw_input("press <return> to continue ...")
|
||||
continue
|
||||
elif inp.lower() in ('v', 'i', 'a'):
|
||||
print show_version_info(about=True)
|
||||
raw_input("press <return> to continue ...")
|
||||
continue
|
||||
|
||||
# options
|
||||
try:
|
||||
inp = int(inp)
|
||||
except ValueError:
|
||||
print "Not a valid option."
|
||||
continue
|
||||
if inp == 1:
|
||||
# start everything, log to log files
|
||||
server_operation("start", "all", False, False)
|
||||
elif inp == 2:
|
||||
# start everything, server interactive start
|
||||
server_operation("start", "all", True, False)
|
||||
elif inp == 3:
|
||||
# start everything, portal interactive start
|
||||
server_operation("start", "server", False, False)
|
||||
server_operation("start", "portal", True, False)
|
||||
elif inp == 4:
|
||||
# start both server and portal interactively
|
||||
server_operation("start", "server", True, False)
|
||||
server_operation("start", "portal", True, False)
|
||||
elif inp == 5:
|
||||
# reload the server
|
||||
server_operation("reload", "server", None, None)
|
||||
elif inp == 6:
|
||||
# reload the portal
|
||||
server_operation("reload", "portal", None, None)
|
||||
elif inp == 7:
|
||||
# stop server and portal
|
||||
server_operation("stop", "all", None, None)
|
||||
elif inp == 8:
|
||||
# stop server
|
||||
server_operation("stop", "server", None, None)
|
||||
elif inp == 9:
|
||||
# stop portal
|
||||
server_operation("stop", "portal", None, None)
|
||||
else:
|
||||
print "Not a valid option."
|
||||
continue
|
||||
return
|
||||
|
||||
|
||||
def server_operation(mode, service, interactive, profiler):
|
||||
"""
|
||||
Handle argument options given on the command line.
|
||||
|
||||
mode - str; start/stop etc
|
||||
service - str; server, portal or all
|
||||
interactive - bool; use interactive mode or daemon
|
||||
profiler - run the service under the profiler
|
||||
"""
|
||||
|
||||
cmdstr = [sys.executable, EVENNIA_RUNNER]
|
||||
errmsg = "The %s does not seem to be running."
|
||||
|
||||
if mode == 'start':
|
||||
|
||||
# launch the error checker. Best to catch the errors already here.
|
||||
error_check_python_modules()
|
||||
|
||||
# starting one or many services
|
||||
if service == 'server':
|
||||
if profiler:
|
||||
cmdstr.append('--pserver')
|
||||
if interactive:
|
||||
cmdstr.append('--iserver')
|
||||
cmdstr.append('--noportal')
|
||||
elif service == 'portal':
|
||||
if profiler:
|
||||
cmdstr.append('--pportal')
|
||||
if interactive:
|
||||
cmdstr.append('--iportal')
|
||||
cmdstr.append('--noserver')
|
||||
django.core.management.call_command('collectstatic', verbosity=1, interactive=False)
|
||||
else: # all
|
||||
# for convenience we don't start logging of
|
||||
# portal, only of server with this command.
|
||||
if profiler:
|
||||
cmdstr.append('--pserver') # this is the common case
|
||||
if interactive:
|
||||
cmdstr.append('--iserver')
|
||||
django.core.management.call_command('collectstatic', verbosity=1, interactive=False)
|
||||
cmdstr.extend([GAMEDIR, TWISTED_BINARY, SERVER_LOGFILE, PORTAL_LOGFILE, HTTP_LOGFILE])
|
||||
# start the server
|
||||
Popen(cmdstr, env=getenv())
|
||||
|
||||
elif mode == 'reload':
|
||||
# restarting services
|
||||
if os.name == 'nt':
|
||||
print "Restarting from command line is not supported under Windows. Log into the game to restart."
|
||||
return
|
||||
if service == 'server':
|
||||
kill(SERVER_PIDFILE, SIG, "Server reloaded.", errmsg % 'Server', SERVER_RESTART, restart=True)
|
||||
elif service == 'portal':
|
||||
print """
|
||||
Note: Portal usually doesnt't need to be reloaded unless you are debugging in interactive mode.
|
||||
If Portal was running in default Daemon mode, it cannot be restarted. In that case you have
|
||||
to restart it manually with 'evennia.py start portal'
|
||||
"""
|
||||
kill(PORTAL_PIDFILE, SIG, "Portal reloaded (or stopped, if it was in daemon mode).", errmsg % 'Portal', PORTAL_RESTART, restart=True)
|
||||
else: # all
|
||||
# default mode, only restart server
|
||||
kill(SERVER_PIDFILE, SIG, "Server reload.", errmsg % 'Server', SERVER_RESTART, restart=True)
|
||||
|
||||
elif mode == 'stop':
|
||||
# stop processes, avoiding reload
|
||||
if service == 'server':
|
||||
kill(SERVER_PIDFILE, SIG, "Server stopped.", errmsg % 'Server', SERVER_RESTART)
|
||||
elif service == 'portal':
|
||||
kill(PORTAL_PIDFILE, SIG, "Portal stopped.", errmsg % 'Portal', PORTAL_RESTART)
|
||||
else:
|
||||
kill(PORTAL_PIDFILE, SIG, "Portal stopped.", errmsg % 'Portal', PORTAL_RESTART)
|
||||
kill(SERVER_PIDFILE, SIG, "Server stopped.", errmsg % 'Server', SERVER_RESTART)
|
||||
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
Run the evennia main program.
|
||||
"""
|
||||
|
||||
# set up argument parser
|
||||
|
||||
parser = ArgumentParser(description=CMDLINE_HELP)
|
||||
parser.add_argument('-v', '--version', action='store_true',
|
||||
dest='show_version', default=False,
|
||||
help="Show version info.")
|
||||
parser.add_argument('-i', '--interactive', action='store_true',
|
||||
dest='interactive', default=False,
|
||||
help="Start given processes in interactive mode.")
|
||||
parser.add_argument('--init', action='store', dest="init", metavar="name",
|
||||
help="Creates a new game directory 'name' at the current location.")
|
||||
parser.add_argument('--profiler', action='store_true', dest='profiler', default=False,
|
||||
help="Start given server component under the Python profiler.")
|
||||
parser.add_argument('--dummyrunner', nargs=1, action='store', dest='dummyrunner', metavar="N",
|
||||
help="Tests a running server by connecting N dummy players to it.")
|
||||
parser.add_argument("mode", metavar="option", nargs='?', default="help",
|
||||
help="Operational mode or management option. Commonly start, stop, reload, migrate, or menu (default).")
|
||||
parser.add_argument("service", metavar="component", nargs='?', choices=["all", "server", "portal"], default="all",
|
||||
help="Which server component to operate on. One of server, portal or all (default).")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# handle arguments
|
||||
|
||||
mode, service = args.mode, args.service
|
||||
|
||||
check_main_evennia_dependencies()
|
||||
|
||||
if args.init:
|
||||
create_game_directory(args.init)
|
||||
print CREATED_NEW_GAMEDIR.format(gamedir=args.init,
|
||||
settings_path=os.path.join(args.init, SETTINGS_PATH))
|
||||
sys.exit()
|
||||
|
||||
if args.show_version:
|
||||
print show_version_info(mode=="help")
|
||||
sys.exit()
|
||||
if mode == "help" and not args.dummyrunner:
|
||||
print ABOUT_INFO
|
||||
sys.exit()
|
||||
check_db = not mode == "migrate"
|
||||
|
||||
# this must be done first - it sets up all the global properties
|
||||
# and initializes django for the game directory
|
||||
init_game_directory(CURRENT_DIR, check_db=check_db)
|
||||
|
||||
if args.dummyrunner:
|
||||
# launch the dummy runner
|
||||
run_dummyrunner(args.dummyrunner[0])
|
||||
elif mode == 'menu':
|
||||
# launch menu for operation
|
||||
run_menu()
|
||||
elif mode in ('start', 'reload', 'stop'):
|
||||
# operate the server directly
|
||||
server_operation(mode, service, args.interactive, args.profiler)
|
||||
else:
|
||||
# pass-through to django manager
|
||||
if mode in ('runserver', 'testserver'):
|
||||
print WARNING_RUNSERVER
|
||||
django.core.management.call_command(mode)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# start Evennia from the command line
|
||||
main()
|
||||
|
|
@ -1,330 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
"""
|
||||
|
||||
This runner is controlled by the evennia launcher and should normally
|
||||
not be launched directly. It manages the two main Evennia processes
|
||||
(Server and Portal) and most importanly runs a passive, threaded loop
|
||||
that makes sure to restart Server whenever it shuts down.
|
||||
|
||||
Since twistd does not allow for returning an optional exit code we
|
||||
need to handle the current reload state for server and portal with
|
||||
flag-files instead. The files, one each for server and portal either
|
||||
contains True or False indicating if the process should be restarted
|
||||
upon returning, or not. A process returning != 0 will always stop, no
|
||||
matter the value of this file.
|
||||
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
from argparse import ArgumentParser
|
||||
from subprocess import Popen
|
||||
import Queue, thread
|
||||
import evennia
|
||||
|
||||
try:
|
||||
# check if launched with pypy
|
||||
import __pypy__ as is_pypy
|
||||
except ImportError:
|
||||
is_pypy = False
|
||||
|
||||
SERVER = None
|
||||
PORTAL = None
|
||||
|
||||
EVENNIA_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
EVENNIA_BIN = os.path.join(EVENNIA_ROOT, "bin")
|
||||
EVENNIA_LIB = os.path.dirname(evennia.__file__)
|
||||
|
||||
SERVER_PY_FILE = os.path.join(EVENNIA_LIB,'server', 'server.py')
|
||||
PORTAL_PY_FILE = os.path.join(EVENNIA_LIB, 'server', 'portal', 'portal.py')
|
||||
|
||||
GAMEDIR = None
|
||||
SERVERDIR = "server"
|
||||
SERVER_PIDFILE = None
|
||||
PORTAL_PIDFILE = None
|
||||
SERVER_RESTART = None
|
||||
PORTAL_RESTART = None
|
||||
SERVER_LOGFILE = None
|
||||
PORTAL_LOGFILE = None
|
||||
HTTP_LOGFILE = None
|
||||
PPROFILER_LOGFILE = None
|
||||
SPROFILER_LOGFILE = None
|
||||
|
||||
# messages
|
||||
|
||||
CMDLINE_HELP = \
|
||||
"""
|
||||
This program manages the running Evennia processes. It is called
|
||||
by evennia and should not be started manually. Its main task is to
|
||||
sit and watch the Server and restart it whenever the user reloads.
|
||||
The runner depends on four files for its operation, two PID files
|
||||
and two RESTART files for Server and Portal respectively; these
|
||||
are stored in the game's server/ directory.
|
||||
"""
|
||||
|
||||
PROCESS_ERROR = \
|
||||
"""
|
||||
{component} process error: {traceback}.
|
||||
"""
|
||||
|
||||
PROCESS_IOERROR = \
|
||||
"""
|
||||
{component} IOError: {traceback}
|
||||
One possible explanation is that 'twistd' was not found.
|
||||
"""
|
||||
|
||||
PROCESS_RESTART = "{component} restarting ..."
|
||||
|
||||
|
||||
# Functions
|
||||
|
||||
def set_restart_mode(restart_file, flag="reload"):
|
||||
"""
|
||||
This sets a flag file for the restart mode.
|
||||
"""
|
||||
with open(restart_file, 'w') as f:
|
||||
f.write(str(flag))
|
||||
|
||||
|
||||
def getenv():
|
||||
"""
|
||||
Get current environment and add PYTHONPATH
|
||||
"""
|
||||
sep = ";" if os.name == "nt" else ":"
|
||||
env = os.environ.copy()
|
||||
env['PYTHONPATH'] = sep.join(sys.path)
|
||||
return env
|
||||
|
||||
|
||||
def get_restart_mode(restart_file):
|
||||
"""
|
||||
Parse the server/portal restart status
|
||||
"""
|
||||
if os.path.exists(restart_file):
|
||||
with open(restart_file, 'r') as f:
|
||||
return f.read()
|
||||
return "shutdown"
|
||||
|
||||
|
||||
def get_pid(pidfile):
|
||||
"""
|
||||
Get the PID (Process ID) by trying to access
|
||||
an PID file.
|
||||
"""
|
||||
pid = None
|
||||
if os.path.exists(pidfile):
|
||||
with open(pidfile, 'r') as f:
|
||||
pid = f.read()
|
||||
return pid
|
||||
|
||||
|
||||
def cycle_logfile(logfile):
|
||||
"""
|
||||
Rotate the old log files to <filename>.old
|
||||
"""
|
||||
logfile_old = logfile + '.old'
|
||||
if os.path.exists(logfile):
|
||||
# Cycle the old logfiles to *.old
|
||||
if os.path.exists(logfile_old):
|
||||
# E.g. Windows don't support rename-replace
|
||||
os.remove(logfile_old)
|
||||
os.rename(logfile, logfile_old)
|
||||
|
||||
# Start program management
|
||||
|
||||
|
||||
def start_services(server_argv, portal_argv):
|
||||
"""
|
||||
This calls a threaded loop that launces the Portal and Server
|
||||
and then restarts them when they finish.
|
||||
"""
|
||||
global SERVER, PORTAL
|
||||
processes = Queue.Queue()
|
||||
|
||||
def server_waiter(queue):
|
||||
try:
|
||||
rc = Popen(server_argv, env=getenv()).wait()
|
||||
except Exception, e:
|
||||
print PROCESS_ERROR.format(component="Server", traceback=e)
|
||||
return
|
||||
# this signals the controller that the program finished
|
||||
queue.put(("server_stopped", rc))
|
||||
|
||||
def portal_waiter(queue):
|
||||
try:
|
||||
rc = Popen(portal_argv, env=getenv()).wait()
|
||||
except Exception, e:
|
||||
print PROCESS_ERROR.format(component="Portal", traceback=e)
|
||||
return
|
||||
# this signals the controller that the program finished
|
||||
queue.put(("portal_stopped", rc))
|
||||
|
||||
if portal_argv:
|
||||
try:
|
||||
if get_restart_mode(PORTAL_RESTART) == "True":
|
||||
# start portal as interactive, reloadable thread
|
||||
PORTAL = thread.start_new_thread(portal_waiter, (processes, ))
|
||||
else:
|
||||
# normal operation: start portal as a daemon;
|
||||
# we don't care to monitor it for restart
|
||||
PORTAL = Popen(portal_argv, env=getenv())
|
||||
except IOError, e:
|
||||
print PROCESS_IOERROR.format(component="Portal", traceback=e)
|
||||
return
|
||||
|
||||
try:
|
||||
if server_argv:
|
||||
# start server as a reloadable thread
|
||||
SERVER = thread.start_new_thread(server_waiter, (processes, ))
|
||||
except IOError, e:
|
||||
print PROCESS_IOERROR.format(component="Server", traceback=e)
|
||||
return
|
||||
|
||||
# Reload loop
|
||||
while True:
|
||||
|
||||
# this blocks until something is actually returned.
|
||||
message, rc = processes.get()
|
||||
|
||||
# restart only if process stopped cleanly
|
||||
if (message == "server_stopped" and int(rc) == 0 and
|
||||
get_restart_mode(SERVER_RESTART) in ("True", "reload", "reset")):
|
||||
print PROCESS_RESTART.format(component="Server")
|
||||
SERVER = thread.start_new_thread(server_waiter, (processes, ))
|
||||
continue
|
||||
|
||||
# normally the portal is not reloaded since it's run as a daemon.
|
||||
if (message == "portal_stopped" and int(rc) == 0 and
|
||||
get_restart_mode(PORTAL_RESTART) == "True"):
|
||||
print PROCESS_RESTART.format(component="Portal")
|
||||
PORTAL = thread.start_new_thread(portal_waiter, (processes, ))
|
||||
continue
|
||||
break
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
This handles the command line input of the runner, usually created by
|
||||
the evennia launcher
|
||||
"""
|
||||
|
||||
parser = ArgumentParser(description=CMDLINE_HELP)
|
||||
parser.add_argument('--noserver', action='store_true', dest='noserver',
|
||||
default=False, help='Do not start Server process')
|
||||
parser.add_argument('--noportal', action='store_true', dest='noportal',
|
||||
default=False, help='Do not start Portal process')
|
||||
parser.add_argument('--iserver', action='store_true', dest='iserver',
|
||||
default=False, help='Server in interactive mode')
|
||||
parser.add_argument('--iportal', action='store_true', dest='iportal',
|
||||
default=False, help='Portal in interactive mode')
|
||||
parser.add_argument('--pserver', action='store_true', dest='pserver',
|
||||
default=False, help='Profile Server')
|
||||
parser.add_argument('--pportal', action='store_true', dest='pportal',
|
||||
default=False, help='Profile Portal')
|
||||
parser.add_argument('--nologcycle', action='store_false', dest='nologcycle',
|
||||
default=True, help='Do not cycle log files')
|
||||
parser.add_argument('gamedir', help="path to game dir")
|
||||
parser.add_argument('twistdbinary', help="path to twistd binary")
|
||||
parser.add_argument('slogfile', help="path to server log file")
|
||||
parser.add_argument('plogfile', help="path to portal log file")
|
||||
parser.add_argument('hlogfile', help="path to http log file")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
global GAMEDIR
|
||||
global SERVER_LOGFILE, PORTAL_LOGFILE, HTTP_LOGFILE
|
||||
global SERVER_PIDFILE, PORTAL_PIDFILE
|
||||
global SERVER_RESTART, PORTAL_RESTART
|
||||
global SPROFILER_LOGFILE, PPROFILER_LOGFILE
|
||||
|
||||
GAMEDIR = args.gamedir
|
||||
sys.path.insert(1, os.path.join(GAMEDIR, SERVERDIR))
|
||||
|
||||
SERVER_PIDFILE = os.path.join(GAMEDIR, SERVERDIR, "server.pid")
|
||||
PORTAL_PIDFILE = os.path.join(GAMEDIR, SERVERDIR, "portal.pid")
|
||||
SERVER_RESTART = os.path.join(GAMEDIR, SERVERDIR, "server.restart")
|
||||
PORTAL_RESTART = os.path.join(GAMEDIR, SERVERDIR, "portal.restart")
|
||||
SERVER_LOGFILE = args.slogfile
|
||||
PORTAL_LOGFILE = args.plogfile
|
||||
HTTP_LOGFILE = args.hlogfile
|
||||
TWISTED_BINARY = args.twistdbinary
|
||||
SPROFILER_LOGFILE = os.path.join(GAMEDIR, SERVERDIR, "logs", "server.prof")
|
||||
PPROFILER_LOGFILE = os.path.join(GAMEDIR, SERVERDIR, "logs", "portal.prof")
|
||||
|
||||
# set up default project calls
|
||||
server_argv = [TWISTED_BINARY,
|
||||
'--nodaemon',
|
||||
'--logfile=%s' % SERVER_LOGFILE,
|
||||
'--pidfile=%s' % SERVER_PIDFILE,
|
||||
'--python=%s' % SERVER_PY_FILE]
|
||||
portal_argv = [TWISTED_BINARY,
|
||||
'--logfile=%s' % PORTAL_LOGFILE,
|
||||
'--pidfile=%s' % PORTAL_PIDFILE,
|
||||
'--python=%s' % PORTAL_PY_FILE]
|
||||
|
||||
# Profiling settings (read file from python shell e.g with
|
||||
# p = pstats.Stats('server.prof')
|
||||
pserver_argv = ['--savestats',
|
||||
'--profiler=cprofile',
|
||||
'--profile=%s' % SPROFILER_LOGFILE]
|
||||
pportal_argv = ['--savestats',
|
||||
'--profiler=cprofile',
|
||||
'--profile=%s' % PPROFILER_LOGFILE]
|
||||
|
||||
# Server
|
||||
|
||||
pid = get_pid(SERVER_PIDFILE)
|
||||
if pid and not args.noserver:
|
||||
print "\nEvennia Server is already running as process %(pid)s. Not restarted." % {'pid': pid}
|
||||
args.noserver = True
|
||||
if args.noserver:
|
||||
server_argv = None
|
||||
else:
|
||||
set_restart_mode(SERVER_RESTART, "shutdown")
|
||||
if args.iserver:
|
||||
# don't log to server logfile
|
||||
del server_argv[2]
|
||||
print "\nStarting Evennia Server (output to stdout)."
|
||||
else:
|
||||
if not args.nologcycle:
|
||||
cycle_logfile(SERVER_LOGFILE)
|
||||
print "\nStarting Evennia Server (output to server logfile)."
|
||||
if args.pserver:
|
||||
server_argv.extend(pserver_argv)
|
||||
print "\nRunning Evennia Server under cProfile."
|
||||
|
||||
# Portal
|
||||
|
||||
pid = get_pid(PORTAL_PIDFILE)
|
||||
if pid and not args.noportal:
|
||||
print "\nEvennia Portal is already running as process %(pid)s. Not restarted." % {'pid': pid}
|
||||
args.noportal = True
|
||||
if args.noportal:
|
||||
portal_argv = None
|
||||
else:
|
||||
if args.iportal:
|
||||
# make portal interactive
|
||||
portal_argv[1] = '--nodaemon'
|
||||
set_restart_mode(PORTAL_RESTART, True)
|
||||
print "\nStarting Evennia Portal in non-Daemon mode (output to stdout)."
|
||||
else:
|
||||
if not args.nologcycle:
|
||||
cycle_logfile(PORTAL_LOGFILE)
|
||||
cycle_logfile(HTTP_LOGFILE)
|
||||
set_restart_mode(PORTAL_RESTART, False)
|
||||
print "\nStarting Evennia Portal in Daemon mode (output to portal logfile)."
|
||||
if args.pportal:
|
||||
portal_argv.extend(pportal_argv)
|
||||
print "\nRunning Evennia Portal under cProfile."
|
||||
|
||||
# Windows fixes (Windows don't support pidfiles natively)
|
||||
if os.name == 'nt':
|
||||
if server_argv:
|
||||
del server_argv[-2]
|
||||
if portal_argv:
|
||||
del portal_argv[-2]
|
||||
|
||||
# Start processes
|
||||
start_services(server_argv, portal_argv)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
|
||||
Dummyrunner
|
||||
|
||||
This is a test system for stress-testing the server. It will launch numbers
|
||||
of "dummy players" to connect to the server and do various sequences of actions.
|
||||
See header of dummyrunner.py for usage.
|
||||
|
|
@ -1,310 +0,0 @@
|
|||
"""
|
||||
Dummy client runner
|
||||
|
||||
This module implements a stand-alone launcher for stress-testing
|
||||
an Evennia game. It will launch any number of fake clients. These
|
||||
clients will log into the server and start doing random operations.
|
||||
Customizing and weighing these operations differently depends on
|
||||
which type of game is tested. The module contains a testing module
|
||||
for plain Evennia.
|
||||
|
||||
Please note that you shouldn't run this on a production server!
|
||||
Launch the program without any arguments or options to see a
|
||||
full step-by-step setup help.
|
||||
|
||||
Basically (for testing default Evennia):
|
||||
|
||||
- Use an empty/testing database.
|
||||
- set PERMISSION_PLAYER_DEFAULT = "Builders"
|
||||
- start server, eventually with profiling active
|
||||
- launch this client runner
|
||||
|
||||
If you want to customize the runner's client actions
|
||||
(because you changed the cmdset or needs to better
|
||||
match your use cases or add more actions), you can
|
||||
change which actions by adding a path to
|
||||
|
||||
DUMMYRUNNER_ACTIONS_MODULE = <path.to.your.module>
|
||||
|
||||
in your settings. See utils.dummyrunner_actions.py
|
||||
for instructions on how to define this module.
|
||||
|
||||
"""
|
||||
|
||||
import time, random
|
||||
from argparse import ArgumentParser
|
||||
from twisted.conch import telnet
|
||||
from twisted.internet import reactor, protocol
|
||||
from twisted.internet.task import LoopingCall
|
||||
|
||||
from django.conf import settings
|
||||
from evennia.utils import mod_import, time_format
|
||||
|
||||
# Load the dummyrunner settings module
|
||||
|
||||
DUMMYRUNNER_SETTINGS = mod_import(settings.DUMMYRUNNER_SETTINGS_MODULE)
|
||||
DATESTRING = "%Y%m%d%H%M%S"
|
||||
|
||||
# Settings
|
||||
|
||||
# number of clients to launch if no input is given on command line
|
||||
NCLIENTS = 1
|
||||
# time between each 'tick', in seconds, if not set on command
|
||||
# line. All launched clients will be called upon to possibly do an
|
||||
# action with this frequency.
|
||||
TIMESTEP = DUMMYRUNNER_SETTINGS.TIMESTEP
|
||||
# chance of a client performing an action, per timestep. This helps to
|
||||
# spread out usage randomly, like it would be in reality.
|
||||
CHANCE_OF_ACTION = DUMMYRUNNER_SETTINGS.CHANCE_OF_ACTION
|
||||
# spread out the login action separately, having many players create accounts
|
||||
# and connect simultaneously is generally unlikely.
|
||||
CHANCE_OF_LOGIN = DUMMYRUNNER_SETTINGS.CHANCE_OF_LOGIN
|
||||
# Port to use, if not specified on command line
|
||||
TELNET_PORT = DUMMYRUNNER_SETTINGS.TELNET_PORT or settings.TELNET_PORTS[0]
|
||||
#
|
||||
NLOGGED_IN = 0
|
||||
|
||||
# Messages
|
||||
|
||||
INFO_STARTING = \
|
||||
"""
|
||||
Dummyrunner starting using {N} dummy player(s). If you don't see
|
||||
any connection messages, make sure that the Evennia server is
|
||||
running. If you intend the dummies to work fully, set
|
||||
settings.PERMISSION_PLAYER_DEFAULT appropriately (if so it is
|
||||
recommended that you use a temporary testing database).
|
||||
|
||||
Use Ctrl-C to stop/disconnect clients.
|
||||
"""
|
||||
|
||||
ERROR_FEW_ACTIONS = \
|
||||
"""
|
||||
Dummyrunner settings error: The ACTIONS tuple is too short: it must
|
||||
contain at least login- and logout functions.
|
||||
"""
|
||||
|
||||
|
||||
HELPTEXT = """
|
||||
DO NOT RUN THIS ON A PRODUCTION SERVER! USE A CLEAN/TESTING DATABASE!
|
||||
|
||||
This stand-alone program launches dummy telnet clients against a
|
||||
running Evennia server. The idea is to mimic real players logging in
|
||||
and repeatedly doing resource-heavy commands so as to stress test the
|
||||
game. It uses the default command set to log in and issue commands, so
|
||||
if that was customized, some of the functionality will not be tested
|
||||
(it will not fail, the commands will just not be recognized). The
|
||||
running clients will create new objects and rooms all over the place
|
||||
as part of their running, so using a clean/testing database is
|
||||
strongly recommended.
|
||||
|
||||
Setup:
|
||||
1) setup a fresh/clean database (if using sqlite, just safe-copy
|
||||
away your real evennia.db3 file and create a new one with
|
||||
manage.py)
|
||||
2) in server/conf/settings.py, add
|
||||
|
||||
PERMISSION_PLAYER_DEFAULT="Builders"
|
||||
|
||||
This is so that the dummy players can test building operations.
|
||||
You can also customize the dummyrunner by modifying a setting
|
||||
file specified by DUMMYRUNNER_SETTINGS_MODULE
|
||||
|
||||
3) Start Evennia like normal, optionally with profiling (--profile)
|
||||
4) Run this dummy runner via the evennia launcher:
|
||||
|
||||
evennia --dummyrunner <nr_of_clients>
|
||||
|
||||
5) Log on and determine if game remains responsive despite the
|
||||
heavier load. Note that if you do profiling, there is an
|
||||
additional overhead from the profiler too!j
|
||||
6) If you use profiling, let the game run long enough to gather
|
||||
data, then stop the server cleanly using evennia stop or @shutdown.
|
||||
@shutdown. The profile appears as
|
||||
server/logs/server.prof/portal.prof (see Python's manual on
|
||||
cProfiler).
|
||||
|
||||
"""
|
||||
|
||||
#------------------------------------------------------------
|
||||
# Helper functions
|
||||
#------------------------------------------------------------
|
||||
|
||||
|
||||
ICOUNT = 0
|
||||
def idcounter():
|
||||
"makes unique ids"
|
||||
global ICOUNT
|
||||
ICOUNT += 1
|
||||
return str(ICOUNT)
|
||||
|
||||
|
||||
GCOUNT = 0
|
||||
def gidcounter():
|
||||
"makes globally unique ids"
|
||||
global GCOUNT
|
||||
GCOUNT += 1
|
||||
return "%s-%s" % (time.strftime(DATESTRING), GCOUNT)
|
||||
|
||||
|
||||
def makeiter(obj):
|
||||
"makes everything iterable"
|
||||
if not hasattr(obj, '__iter__'):
|
||||
return [obj]
|
||||
return obj
|
||||
|
||||
#------------------------------------------------------------
|
||||
# Client classes
|
||||
#------------------------------------------------------------
|
||||
|
||||
class DummyClient(telnet.StatefulTelnetProtocol):
|
||||
"""
|
||||
Handles connection to a running Evennia server,
|
||||
mimicking a real player by sending commands on
|
||||
a timer.
|
||||
"""
|
||||
|
||||
def connectionMade(self):
|
||||
|
||||
# public properties
|
||||
self.cid = idcounter()
|
||||
self.key = "Dummy-%s" % self.cid
|
||||
self.gid = "%s-%s" % (time.strftime(DATESTRING), self.cid)
|
||||
self.istep = 0
|
||||
self.exits = [] # exit names created
|
||||
self.objs = [] # obj names created
|
||||
|
||||
self._loggedin = False
|
||||
self._logging_out = False
|
||||
self._report = ""
|
||||
self._cmdlist = [] # already stepping in a cmd definition
|
||||
self._login = self.factory.actions[0]
|
||||
self._logout = self.factory.actions[1]
|
||||
self._actions = self.factory.actions[2:]
|
||||
|
||||
reactor.addSystemEventTrigger('before', 'shutdown', self.logout)
|
||||
|
||||
# start client tick
|
||||
d = LoopingCall(self.step)
|
||||
# dissipate exact step by up to +/- 0.5 second
|
||||
timestep = TIMESTEP + (-0.5 + (random.random()*1.0))
|
||||
d.start(timestep, now=True).addErrback(self.error)
|
||||
|
||||
|
||||
def dataReceived(self, data):
|
||||
"Echo incoming data to stdout"
|
||||
pass
|
||||
|
||||
def connectionLost(self, reason):
|
||||
"loosing the connection"
|
||||
if not self._logging_out:
|
||||
print "client %s(%s) lost connection (%s)" % (self.key, self.cid, reason)
|
||||
|
||||
def error(self, err):
|
||||
"error callback"
|
||||
print err
|
||||
|
||||
def counter(self):
|
||||
"produces a unique id, also between clients"
|
||||
return gidcounter()
|
||||
|
||||
def logout(self):
|
||||
"Causes the client to log out of the server. Triggered by ctrl-c signal."
|
||||
self._logging_out = True
|
||||
cmd = self._logout(self)
|
||||
print "client %s(%s) logout (%s actions)" % (self.key, self.cid, self.istep)
|
||||
self.sendLine(cmd)
|
||||
|
||||
def step(self):
|
||||
"""
|
||||
Perform a step. This is called repeatedly by the runner
|
||||
and causes the client to issue commands to the server.
|
||||
This holds all "intelligence" of the dummy client.
|
||||
"""
|
||||
global NLOGGED_IN
|
||||
|
||||
rand = random.random()
|
||||
|
||||
if not self._cmdlist:
|
||||
# no commands ready. Load some.
|
||||
|
||||
if not self._loggedin:
|
||||
if rand < CHANCE_OF_LOGIN:
|
||||
# get the login commands
|
||||
self._cmdlist = list(makeiter(self._login(self)))
|
||||
NLOGGED_IN += 1 # this is for book-keeping
|
||||
print "connecting client %s (%i/%i)..." % (self.key, NLOGGED_IN, NCLIENTS)
|
||||
self._loggedin = True
|
||||
else:
|
||||
# no login yet, so cmdlist not yet set
|
||||
return
|
||||
else:
|
||||
# we always pick a cumulatively random function
|
||||
crand = random.random()
|
||||
cfunc = [func for (cprob, func) in self._actions if cprob >= crand][0]
|
||||
self._cmdlist = list(makeiter(cfunc(self)))
|
||||
|
||||
# at this point we always have a list of commands
|
||||
if rand < CHANCE_OF_ACTION:
|
||||
# send to the game
|
||||
self.sendLine(str(self._cmdlist.pop(0)))
|
||||
self.istep += 1
|
||||
|
||||
|
||||
class DummyFactory(protocol.ClientFactory):
|
||||
protocol = DummyClient
|
||||
def __init__(self, actions):
|
||||
"Setup the factory base (shared by all clients)"
|
||||
self.actions = actions
|
||||
|
||||
#------------------------------------------------------------
|
||||
# Access method:
|
||||
# Starts clients and connects them to a running server.
|
||||
#------------------------------------------------------------
|
||||
|
||||
def start_all_dummy_clients(nclients):
|
||||
|
||||
global NCLIENTS
|
||||
NCLIENTS = int(nclients)
|
||||
actions = DUMMYRUNNER_SETTINGS.ACTIONS
|
||||
|
||||
if len(actions) < 2:
|
||||
print ERROR_FEW_ACTIONS
|
||||
return
|
||||
|
||||
# make sure the probabilities add up to 1
|
||||
pratio = 1.0 / sum(tup[0] for tup in actions[2:])
|
||||
flogin, flogout, probs, cfuncs = actions[0], actions[1], [tup[0] * pratio for tup in actions[2:]], [tup[1] for tup in actions[2:]]
|
||||
# create cumulative probabilies for the random actions
|
||||
cprobs = [sum(v for i,v in enumerate(probs) if i<=k) for k in range(len(probs))]
|
||||
# rebuild a new, optimized action structure
|
||||
actions = (flogin, flogout) + tuple(zip(cprobs, cfuncs))
|
||||
|
||||
# setting up all clients (they are automatically started)
|
||||
factory = DummyFactory(actions)
|
||||
for i in range(NCLIENTS):
|
||||
reactor.connectTCP("localhost", TELNET_PORT, factory)
|
||||
# start reactor
|
||||
reactor.run()
|
||||
|
||||
#------------------------------------------------------------
|
||||
# Command line interface
|
||||
#------------------------------------------------------------
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
# parsing command line with default vals
|
||||
parser = ArgumentParser(description=HELPTEXT)
|
||||
parser.add_argument("-N", nargs=1, default=1, dest="nclients",
|
||||
help="Number of clients to start")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
print INFO_STARTING.format(N=args.nclients[0])
|
||||
|
||||
# run the dummyrunner
|
||||
t0 = time.time()
|
||||
start_all_dummy_clients(nclients=args.nclients[0])
|
||||
ttot = time.time() - t0
|
||||
|
||||
# output runtime
|
||||
print "... dummy client runner stopped after %s." % time_format(ttot, style=3)
|
||||
|
|
@ -1,267 +0,0 @@
|
|||
"""
|
||||
Settings and actions for the dummyrunner
|
||||
|
||||
This module defines dummyrunner settings and sets up
|
||||
the actions available to dummy players.
|
||||
|
||||
The settings are global variables:
|
||||
|
||||
TIMESTEP - time in seconds between each 'tick'
|
||||
CHANCE_OF_ACTION - chance 0-1 of action happening
|
||||
CHANCE_OF_LOGIN - chance 0-1 of login happening
|
||||
TELNET_PORT - port to use, defaults to settings.TELNET_PORT
|
||||
ACTIONS - see below
|
||||
|
||||
ACTIONS is a tuple
|
||||
|
||||
(login_func, logout_func, (0.3, func1), (0.1, func2) ... )
|
||||
|
||||
where the first entry is the function to call on first connect, with a
|
||||
chance of occurring given by CHANCE_OF_LOGIN. This function is usually
|
||||
responsible for logging in the player. The second entry is always
|
||||
called when the dummyrunner disconnects from the server and should
|
||||
thus issue a logout command. The other entries are tuples (chance,
|
||||
func). They are picked randomly, their commonality based on the
|
||||
cumulative chance given (the chance is normalized between all options
|
||||
so if will still work also if the given chances don't add up to 1).
|
||||
Since each function can return a list of game-command strings, each
|
||||
function may result in multiple operations.
|
||||
|
||||
An action-function is called with a "client" argument which is a
|
||||
reference to the dummy client currently performing the action. It
|
||||
returns a string or a list of command strings to execute. Use the
|
||||
client object for optionally saving data between actions.
|
||||
|
||||
The client object has the following relevant properties and methods:
|
||||
key - an optional client key. This is only used for dummyrunner output.
|
||||
Default is "Dummy-<cid>"
|
||||
cid - client id
|
||||
gid - globally unique id, hashed with time stamp
|
||||
istep - the current step
|
||||
exits - an empty list. Can be used to store exit names
|
||||
objs - an empty list. Can be used to store object names
|
||||
counter() - returns a unique increasing id, hashed with time stamp
|
||||
to make it unique also between dummyrunner instances.
|
||||
|
||||
The return should either be a single command string or a tuple of
|
||||
command strings. This list of commands will always be executed every
|
||||
TIMESTEP with a chance given by CHANCE_OF_ACTION by in the order given
|
||||
(no randomness) and allows for setting up a more complex chain of
|
||||
commands (such as creating an account and logging in).
|
||||
|
||||
"""
|
||||
# Dummy runner settings
|
||||
|
||||
# Time between each dummyrunner "tick", in seconds. Each dummy
|
||||
# will be called with this frequency.
|
||||
TIMESTEP = 2
|
||||
|
||||
# Chance of a dummy actually performing an action on a given tick.
|
||||
# This spreads out usage randomly, like it would be in reality.
|
||||
CHANCE_OF_ACTION = 0.20
|
||||
|
||||
# Chance of a currently unlogged-in dummy performing its login
|
||||
# action every tick. This emulates not all players logging in
|
||||
# at exactly the same time.
|
||||
CHANCE_OF_LOGIN = 0.10
|
||||
|
||||
# Which telnet port to connect to. If set to None, uses the first
|
||||
# default telnet port of the running server.
|
||||
TELNET_PORT = None
|
||||
|
||||
|
||||
# Setup actions tuple
|
||||
|
||||
# some convenient templates
|
||||
|
||||
DUMMY_NAME = "Dummy-%s"
|
||||
DUMMY_PWD = "password-%s"
|
||||
START_ROOM = "testing_room_start_%s"
|
||||
ROOM_TEMPLATE = "testing_room_%s"
|
||||
EXIT_TEMPLATE = "exit_%s"
|
||||
OBJ_TEMPLATE = "testing_obj_%s"
|
||||
TOBJ_TEMPLATE = "testing_button_%s"
|
||||
TOBJ_TYPECLASS = "contrib.tutorial_examples.red_button.RedButton"
|
||||
|
||||
|
||||
# action function definitions (pick and choose from
|
||||
# these to build a client "usage profile"
|
||||
|
||||
# login/logout
|
||||
|
||||
def c_login(client):
|
||||
"logins to the game"
|
||||
# we always use a new client name
|
||||
cname = DUMMY_NAME % client.gid
|
||||
cpwd = DUMMY_PWD % client.gid
|
||||
|
||||
# set up for digging a first room (to move to and keep the
|
||||
# login room clean)
|
||||
roomname = ROOM_TEMPLATE % client.counter()
|
||||
exitname1 = EXIT_TEMPLATE % client.counter()
|
||||
exitname2 = EXIT_TEMPLATE % client.counter()
|
||||
client.exits.extend([exitname1, exitname2])
|
||||
|
||||
cmds = ('create %s %s' % (cname, cpwd),
|
||||
'connect %s %s' % (cname, cpwd),
|
||||
'@dig %s' % START_ROOM % client.gid,
|
||||
'@teleport %s' % START_ROOM % client.gid,
|
||||
'@dig %s = %s, %s' % (roomname, exitname1, exitname2)
|
||||
)
|
||||
return cmds
|
||||
|
||||
def c_login_nodig(client):
|
||||
"logins, don't dig its own room"
|
||||
cname = DUMMY_NAME % client.gid
|
||||
cpwd = DUMMY_PWD % client.gid
|
||||
|
||||
cmds = ('create %s %s' % (cname, cpwd),
|
||||
'connect %s %s' % (cname, cpwd))
|
||||
return cmds
|
||||
|
||||
def c_logout(client):
|
||||
"logouts of the game"
|
||||
return "@quit"
|
||||
|
||||
# random commands
|
||||
|
||||
def c_looks(client):
|
||||
"looks at various objects"
|
||||
cmds = ["look %s" % obj for obj in client.objs]
|
||||
if not cmds:
|
||||
cmds = ["look %s" % exi for exi in client.exits]
|
||||
if not cmds:
|
||||
cmds = "look"
|
||||
return cmds
|
||||
|
||||
def c_examines(client):
|
||||
"examines various objects"
|
||||
cmds = ["examine %s" % obj for obj in client.objs]
|
||||
if not cmds:
|
||||
cmds = ["examine %s" % exi for exi in client.exits]
|
||||
if not cmds:
|
||||
cmds = "examine me"
|
||||
return cmds
|
||||
|
||||
def c_help(client):
|
||||
"reads help files"
|
||||
cmds = ('help',
|
||||
'help @teleport',
|
||||
'help look',
|
||||
'help @tunnel',
|
||||
'help @dig')
|
||||
return cmds
|
||||
|
||||
def c_digs(client):
|
||||
"digs a new room, storing exit names on client"
|
||||
roomname = ROOM_TEMPLATE % client.counter()
|
||||
exitname1 = EXIT_TEMPLATE % client.counter()
|
||||
exitname2 = EXIT_TEMPLATE % client.counter()
|
||||
client.exits.extend([exitname1, exitname2])
|
||||
return '@dig/tel %s = %s, %s' % (roomname, exitname1, exitname2)
|
||||
|
||||
def c_creates_obj(client):
|
||||
"creates normal objects, storing their name on client"
|
||||
objname = OBJ_TEMPLATE % client.counter()
|
||||
client.objs.append(objname)
|
||||
cmds = ('@create %s' % objname,
|
||||
'@desc %s = "this is a test object' % objname,
|
||||
'@set %s/testattr = this is a test attribute value.' % objname,
|
||||
'@set %s/testattr2 = this is a second test attribute.' % objname)
|
||||
return cmds
|
||||
|
||||
def c_creates_button(client):
|
||||
"creates example button, storing name on client"
|
||||
objname = TOBJ_TEMPLATE % client.counter()
|
||||
client.objs.append(objname)
|
||||
cmds = ('@create %s:%s' % (objname, TOBJ_TYPECLASS),
|
||||
'@desc %s = test red button!' % objname)
|
||||
return cmds
|
||||
|
||||
def c_socialize(client):
|
||||
"socializechats on channel"
|
||||
cmds = ('ooc Hello!',
|
||||
'ooc Testing ...',
|
||||
'ooc Testing ... times 2',
|
||||
'say Yo!',
|
||||
'emote stands looking around.')
|
||||
return cmds
|
||||
|
||||
def c_moves(client):
|
||||
"moves to a previously created room, using the stored exits"
|
||||
cmds = client.exits # try all exits - finally one will work
|
||||
return "look" if not cmds else cmds
|
||||
|
||||
def c_moves_n(client):
|
||||
"move through north exit if available"
|
||||
return "north"
|
||||
|
||||
def c_moves_s(client):
|
||||
"move through south exit if available"
|
||||
return "south"
|
||||
|
||||
# Action tuple (required)
|
||||
#
|
||||
# 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
|
||||
# STARTROOM . The second element is the logout command, for cleanly
|
||||
# exiting the mud. The following elements are 2-tuples of (probability,
|
||||
# action_function). The probablities should normally sum up to 1,
|
||||
# otherwise the system will normalize them.
|
||||
#
|
||||
|
||||
## "normal builder" definitionj
|
||||
#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.3, c_moves))
|
||||
## "heavy" builder definition
|
||||
#ACTIONS = ( c_login,
|
||||
# c_logout,
|
||||
# (0.2, c_looks),
|
||||
# (0.1, c_examines),
|
||||
# (0.2, c_help),
|
||||
# (0.1, c_digs),
|
||||
# (0.1, c_creates_obj),
|
||||
# #(0.01, c_creates_button),
|
||||
# (0.2, c_moves))
|
||||
## "passive player" definition
|
||||
#ACTIONS = ( c_login,
|
||||
# c_logout,
|
||||
# (0.7, c_looks),
|
||||
# #(0.1, c_examines),
|
||||
# (0.3, c_help))
|
||||
# #(0.1, c_digs),
|
||||
# #(0.1, c_creates_obj),
|
||||
# #(0.1, c_creates_button),
|
||||
# #(0.4, c_moves))
|
||||
## "normal player" definition
|
||||
ACTIONS = ( c_login,
|
||||
c_logout,
|
||||
(0.01, c_digs),
|
||||
(0.39, c_looks),
|
||||
(0.2, c_help),
|
||||
(0.4, c_moves))
|
||||
# walking tester. This requires a pre-made
|
||||
# "loop" of multiple rooms that ties back
|
||||
# to limbo (using @tunnel and @open)
|
||||
#ACTIONS = (c_login_nodig,
|
||||
# c_logout,
|
||||
# (1.0, c_moves_n))
|
||||
## "socializing heavy builder" definition
|
||||
#ACTIONS = (c_login,
|
||||
# c_logout,
|
||||
# (0.1, c_socialize),
|
||||
# (0.1, c_looks),
|
||||
# (0.2, c_help),
|
||||
# (0.1, c_creates_obj),
|
||||
# (0.2, c_digs),
|
||||
# (0.3, c_moves))
|
||||
## "heavy digger memory tester" definition
|
||||
#ACTIONS = (c_login,
|
||||
# c_logout,
|
||||
# (1.0, c_digs))
|
||||
|
|
@ -1,101 +0,0 @@
|
|||
"""
|
||||
Script that saves memory and idmapper data over time.
|
||||
|
||||
Data will be saved to game/logs/memoryusage.log. Note that
|
||||
the script will append to this file if it already exists.
|
||||
|
||||
Call this module directly to plot the log (requires matplotlib and numpy).
|
||||
"""
|
||||
import os, sys
|
||||
import time
|
||||
#TODO!
|
||||
#sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))))
|
||||
#os.environ['DJANGO_SETTINGS_MODULE'] = 'game.settings'
|
||||
import ev
|
||||
from evennia.utils.idmapper import base as _idmapper
|
||||
|
||||
LOGFILE = "logs/memoryusage.log"
|
||||
INTERVAL = 30 # log every 30 seconds
|
||||
|
||||
class Memplot(ev.Script):
|
||||
def at_script_creation(self):
|
||||
self.key = "memplot"
|
||||
self.desc = "Save server memory stats to file"
|
||||
self.start_delay = False
|
||||
self.persistent = True
|
||||
self.interval = INTERVAL
|
||||
self.db.starttime = time.time()
|
||||
|
||||
def at_repeat(self):
|
||||
|
||||
pid = os.getpid()
|
||||
rmem = float(os.popen('ps -p %d -o %s | tail -1' % (pid, "rss")).read()) / 1000.0 # resident memory
|
||||
vmem = float(os.popen('ps -p %d -o %s | tail -1' % (pid, "vsz")).read()) / 1000.0 # virtual memory
|
||||
total_num, cachedict = _idmapper.cache_size()
|
||||
t0 = (time.time() - self.db.starttime) / 60.0 # save in minutes
|
||||
|
||||
with open(LOGFILE, "a") as f:
|
||||
f.write("%s, %s, %s, %s\n" % (t0, rmem, vmem, int(total_num)))
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
# plot output from the file
|
||||
|
||||
from matplotlib import pyplot as pp
|
||||
import numpy
|
||||
|
||||
data = numpy.genfromtxt("../../../game/" + LOGFILE, delimiter=",")
|
||||
secs = data[:,0]
|
||||
rmem = data[:,1]
|
||||
vmem = data[:,2]
|
||||
nobj = data[:,3]
|
||||
|
||||
# calculate derivative of obj creation
|
||||
#oderiv = (0.5*(nobj[2:] - nobj[:-2]) / (secs[2:] - secs[:-2])).copy()
|
||||
#oderiv = (0.5*(rmem[2:] - rmem[:-2]) / (secs[2:] - secs[:-2])).copy()
|
||||
|
||||
fig = pp.figure()
|
||||
ax1 = fig.add_subplot(111)
|
||||
ax1.set_title("1000 bots (normal players with light building)")
|
||||
ax1.set_xlabel("Time (mins)")
|
||||
ax1.set_ylabel("Memory usage (MB)")
|
||||
ax1.plot(secs, rmem, "r", label="RMEM", lw=2)
|
||||
ax1.plot(secs, vmem, "b", label="VMEM", lw=2)
|
||||
ax1.legend(loc="upper left")
|
||||
|
||||
ax2 = ax1.twinx()
|
||||
ax2.plot(secs, nobj, "g--", label="objs in cache", lw=2)
|
||||
#ax2.plot(secs[:-2], oderiv/60.0, "g--", label="Objs/second", lw=2)
|
||||
#ax2.plot(secs[:-2], oderiv, "g--", label="Objs/second", lw=2)
|
||||
ax2.set_ylabel("Number of objects")
|
||||
ax2.legend(loc="lower right")
|
||||
ax2.annotate("First 500 bots\nconnecting", xy=(10, 4000))
|
||||
ax2.annotate("Next 500 bots\nconnecting", xy=(350,10000))
|
||||
#ax2.annotate("@reload", xy=(185,600))
|
||||
|
||||
# # plot mem vs cachesize
|
||||
# nobj, rmem, vmem = nobj[:262].copy(), rmem[:262].copy(), vmem[:262].copy()
|
||||
#
|
||||
# fig = pp.figure()
|
||||
# ax1 = fig.add_subplot(111)
|
||||
# ax1.set_title("Memory usage per cache size")
|
||||
# ax1.set_xlabel("Cache size (number of objects)")
|
||||
# ax1.set_ylabel("Memory usage (MB)")
|
||||
# ax1.plot(nobj, rmem, "r", label="RMEM", lw=2)
|
||||
# ax1.plot(nobj, vmem, "b", label="VMEM", lw=2)
|
||||
#
|
||||
|
||||
## # empirical estimate of memory usage: rmem = 35.0 + 0.0157 * Ncache
|
||||
## # Ncache = int((rmem - 35.0) / 0.0157) (rmem in MB)
|
||||
#
|
||||
# rderiv_aver = 0.0157
|
||||
# fig = pp.figure()
|
||||
# ax1 = fig.add_subplot(111)
|
||||
# ax1.set_title("Relation between memory and cache size")
|
||||
# ax1.set_xlabel("Memory usage (MB)")
|
||||
# ax1.set_ylabel("Idmapper Cache Size (number of objects)")
|
||||
# rmem = numpy.linspace(35, 2000, 2000)
|
||||
# nobjs = numpy.array([int((mem - 35.0) / 0.0157) for mem in rmem])
|
||||
# ax1.plot(rmem, nobjs, "r", lw=2)
|
||||
|
||||
pp.show()
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
"""
|
||||
This is a little routine for viewing the sql queries that are executed by a given
|
||||
query as well as count them for optimization testing.
|
||||
|
||||
"""
|
||||
import sys, os
|
||||
#sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))))
|
||||
#os.environ["DJANGO_SETTINGS_MODULE"] = "game.settings"
|
||||
from django.db import connection
|
||||
|
||||
|
||||
def count_queries(exec_string, setup_string):
|
||||
"""
|
||||
Display queries done by exec_string. Use setup_string
|
||||
to setup the environment to test.
|
||||
"""
|
||||
|
||||
exec setup_string
|
||||
|
||||
num_queries_old = len(connection.queries)
|
||||
exec exec_string
|
||||
nqueries = len(connection.queries) - num_queries_old
|
||||
|
||||
for query in connection.queries[-nqueries if nqueries else 1:]:
|
||||
print query["time"], query["sql"]
|
||||
print "Number of queries: %s" % nqueries
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
# setup tests here
|
||||
|
||||
setup_string = \
|
||||
"""
|
||||
from evennia.objects.models import ObjectDB
|
||||
g = ObjectDB.objects.get(db_key="Griatch")
|
||||
"""
|
||||
exec_string = \
|
||||
"""
|
||||
g.tags.all()
|
||||
"""
|
||||
count_queries(exec_string, setup_string)
|
||||
11
bin/unix/evennia
Executable file
11
bin/unix/evennia
Executable file
|
|
@ -0,0 +1,11 @@
|
|||
#! /usr/bin/python2.7
|
||||
"""
|
||||
Linux launcher
|
||||
"""
|
||||
|
||||
import os, sys
|
||||
|
||||
sys.path.insert(0, os.path.abspath(os.getcwd()))
|
||||
|
||||
from evennia.server.evennia_launcher import main
|
||||
main()
|
||||
1
bin/windows/evennia.bat
Normal file
1
bin/windows/evennia.bat
Normal file
|
|
@ -0,0 +1 @@
|
|||
@"python" "evennia.py" %*
|
||||
11
bin/windows/evennia.py
Executable file
11
bin/windows/evennia.py
Executable file
|
|
@ -0,0 +1,11 @@
|
|||
#! /usr/bin/python2.7
|
||||
"""
|
||||
Linux launcher
|
||||
"""
|
||||
|
||||
import os, sys
|
||||
|
||||
sys.path.insert(0, os.path.abspath(os.getcwd()))
|
||||
|
||||
from evennia.server.evennia_launcher import main
|
||||
main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue