Evennia now runs on its own Twisted webserver (no need for testserver or Apache if you don't want to). Evennia now also has an ajax long-polling web client running from Twisted. The web client requires no extra dependencies beyond jQuery which is included. The src/server structure has been r

cleaned up and rewritten to make it easier to add new protocols in the future - all new protocols need to inherit from server.session.Session, whi
ch implements a set of hooks that Evennia uses to communicate. The current web client protocol is functional but does not implement any of rcaskey
's suggestions as of yet - it uses a separate data object passed through msg() to communicate between the server and the various protocols. Also the client itself could probably need cleanup and 'prettification'. The fact that the system runs a hybrid of Django and Twisted, getting the best of both worlds should allow for many possibilities in the future. /Griatch
This commit is contained in:
Griatch 2010-12-07 02:34:59 +00:00
parent ecefbfac01
commit 251f94aa7a
118 changed files with 9049 additions and 593 deletions

View file

@ -1,6 +1,11 @@
"""
This module implements the main Evennia
server process, the core of the game engine.
This module implements the main Evennia server process, the core of
the game engine. Only import this once!
This module should be started with the 'twistd' executable since it
sets up all the networking features. (this is done by
game/evennia.py).
"""
import time
import sys
@ -12,75 +17,82 @@ if os.name == 'nt':
from twisted.application import internet, service
from twisted.internet import protocol, reactor
from twisted.web import server, static
from django.db import connection
from django.conf import settings
from src.utils import reloads
from src.config.models import ConfigValue
from src.server.session import SessionProtocol
from src.server import sessionhandler
from src.server import initial_setup
from src.utils import reloads
from src.utils.utils import get_evennia_version
from src.comms import channelhandler
class EvenniaService(service.Service):
#------------------------------------------------------------
# Evennia Server settings
#------------------------------------------------------------
SERVERNAME = settings.SERVERNAME
VERSION = get_evennia_version()
TELNET_PORTS = settings.TELNET_PORTS
WEBSERVER_PORTS = settings.WEBSERVER_PORTS
TELNET_ENABLED = settings.TELNET_ENABLED and TELNET_PORTS
WEBSERVER_ENABLED = settings.WEBSERVER_ENABLED and WEBSERVER_PORTS
WEBCLIENT_ENABLED = settings.WEBCLIENT_ENABLED
IMC2_ENABLED = settings.IMC2_ENABLED
IRC_ENABLED = settings.IRC_ENABLED
#------------------------------------------------------------
# Evennia Main Server object
#------------------------------------------------------------
class Evennia(object):
"""
The main server service task.
The main Evennia server handler. This object sets up the database and
tracks and interlinks all the twisted network services that make up
evennia.
"""
def __init__(self):
# Holds the TCP services.
self.service_collection = None
self.game_running = True
def __init__(self, application):
"""
Setup the server.
application - an instantiated Twisted application
"""
sys.path.append('.')
# create a store of services
self.services = service.IServiceCollection(application)
print '\n' + '-'*50
# Database-specific startup optimizations.
if (settings.DATABASE_ENGINE == "sqlite3"
or hasattr(settings, 'DATABASE')
and settings.DATABASE.get('ENGINE', None) == 'django.db.backends.sqlite3'):
# run sqlite3 preps
self.sqlite3_prep()
# Begin startup debug output.
print '\n' + '-'*50
last_initial_setup_step = \
ConfigValue.objects.conf('last_initial_setup_step')
if not last_initial_setup_step:
# None is only returned if the config does not exist,
# i.e. this is an empty DB that needs populating.
print ' Server started for the first time. Setting defaults.'
initial_setup.handle_setup(0)
print '-'*50
elif int(last_initial_setup_step) >= 0:
# last_setup_step >= 0 means the setup crashed
# on one of its modules and setup will resume, retrying
# the last failed module. When all are finished, the step
# is set to -1 to show it does not need to be run again.
print ' Resuming initial setup from step %s.' % \
last_initial_setup_step
initial_setup.handle_setup(int(last_initial_setup_step))
print '-'*50
self.sqlite3_prep()
# Run the initial setup if needed
self.run_initial_setup()
# we have to null this here.
sessionhandler.change_session_count(0)
sessionhandler.SESSIONS.session_count(0)
self.start_time = time.time()
# initialize channelhandler
channelhandler.CHANNELHANDLER.update()
# init all global scripts
reloads.reload_scripts(init_mode=True)
# Make output to the terminal.
print ' %s (%s) started on port(s):' % \
(settings.SERVERNAME, get_evennia_version())
for port in settings.GAMEPORTS:
print ' * %s' % (port)
print '-'*50
# Make info output to the terminal.
self.terminal_output()
print '-'*50
self.game_running = True
# Server startup methods
@ -89,14 +101,50 @@ class EvenniaService(service.Service):
Optimize some SQLite stuff at startup since we
can't save it to the database.
"""
cursor = connection.cursor()
cursor.execute("PRAGMA cache_size=10000")
cursor.execute("PRAGMA synchronous=OFF")
cursor.execute("PRAGMA count_changes=OFF")
cursor.execute("PRAGMA temp_store=2")
if (settings.DATABASE_ENGINE == "sqlite3"
or hasattr(settings, 'DATABASE')
and settings.DATABASE.get('ENGINE', None)
== 'django.db.backends.sqlite3'):
cursor = connection.cursor()
cursor.execute("PRAGMA cache_size=10000")
cursor.execute("PRAGMA synchronous=OFF")
cursor.execute("PRAGMA count_changes=OFF")
cursor.execute("PRAGMA temp_store=2")
# General methods
def run_initial_setup(self):
"""
This attempts to run the initial_setup script of the server.
It returns if this is not the first time the server starts.
"""
last_initial_setup_step = ConfigValue.objects.conf('last_initial_setup_step')
if not last_initial_setup_step:
# None is only returned if the config does not exist,
# i.e. this is an empty DB that needs populating.
print ' Server started for the first time. Setting defaults.'
initial_setup.handle_setup(0)
print '-'*50
elif int(last_initial_setup_step) >= 0:
# a positive value means the setup crashed on one of its
# modules and setup will resume from this step, retrying
# the last failed module. When all are finished, the step
# is set to -1 to show it does not need to be run again.
print ' Resuming initial setup from step %s.' % \
last_initial_setup_step
initial_setup.handle_setup(int(last_initial_setup_step))
print '-'*50
def terminal_output(self):
"""
Outputs server startup info to the terminal.
"""
print ' %s (%s) started on port(s):' % (SERVERNAME, VERSION)
if TELNET_ENABLED:
print " telnet: " + ", ".join([str(port) for port in TELNET_PORTS])
if WEBSERVER_ENABLED:
clientstring = ""
if WEBCLIENT_ENABLED:
clientstring = '/client'
print " webserver%s: " % clientstring + ", ".join([str(port) for port in WEBSERVER_PORTS])
def shutdown(self, message=None):
"""
@ -104,52 +152,88 @@ class EvenniaService(service.Service):
"""
if not message:
message = 'The server has been shutdown. Please check back soon.'
sessionhandler.announce_all(message)
sessionhandler.disconnect_all_sessions()
sessionhandler.SESSIONS.disconnect_all_sessions(reason=message)
reactor.callLater(0, reactor.stop)
def getEvenniaServiceFactory(self):
"Retrieve instances of the server"
#------------------------------------------------------------
#
# Start the Evennia game server and add all active services
#
#------------------------------------------------------------
# twistd requires us to define the variable 'application' so it knows
# what to execute from.
application = service.Application('Evennia')
# The main evennia server program. This sets up the database
# and is where we store all the other services.
EVENNIA = Evennia(application)
# We group all the various services under the same twisted app.
# These will gradually be started as they are initialized below.
if TELNET_ENABLED:
# start telnet game connections
from src.server import telnet
for port in TELNET_PORTS:
factory = protocol.ServerFactory()
factory.protocol = SessionProtocol
factory.server = self
return factory
factory.protocol = telnet.TelnetProtocol
telnet_service = internet.TCPServer(port, factory)
telnet_service.setName('Evennia%s' % port)
EVENNIA.services.addService(telnet_service)
def start_services(self, application):
"""
Starts all of the TCP services.
"""
self.service_collection = service.IServiceCollection(application)
for port in settings.GAMEPORTS:
evennia_server = \
internet.TCPServer(port, self.getEvenniaServiceFactory())
evennia_server.setName('Evennia%s' %port)
evennia_server.setServiceParent(self.service_collection)
if settings.IMC2_ENABLED:
from src.imc2.connection import IMC2ClientFactory
from src.imc2 import events as imc2_events
imc2_factory = IMC2ClientFactory()
svc = internet.TCPClient(settings.IMC2_SERVER_ADDRESS,
settings.IMC2_SERVER_PORT,
imc2_factory)
svc.setName('IMC2')
svc.setServiceParent(self.service_collection)
imc2_events.add_events()
if WEBSERVER_ENABLED:
if settings.IRC_ENABLED:
from src.irc.connection import IRC_BotFactory
irc = internet.TCPClient(settings.IRC_NETWORK,
settings.IRC_PORT,
IRC_BotFactory(settings.IRC_CHANNEL,
settings.IRC_NETWORK,
settings.IRC_NICKNAME))
irc.setName("%s:%s" % ("IRC", settings.IRC_CHANNEL))
irc.setServiceParent(self.service_collection)
# a django-compatible webserver.
from src.server.webserver import DjangoWebRoot
# define the root url (/) as a wsgi resource recognized by Django
web_root = DjangoWebRoot()
# point our media resources to url /media
media_dir = os.path.join(settings.SRC_DIR, 'web', settings.MEDIA_URL.lstrip('/')) #TODO: Could be made cleaner?
web_root.putChild("media", static.File(media_dir))
if WEBCLIENT_ENABLED:
# create ajax client processes at /webclientdata
from src.server.webclient import WebClient
web_root.putChild("webclientdata", WebClient())
web_site = server.Site(web_root, logPath=settings.HTTP_LOG_FILE)
for port in WEBSERVER_PORTS:
# create the webserver
webserver = internet.TCPServer(port, web_site)
webserver.setName('EvenniaWebServer%s' % port)
EVENNIA.services.addService(webserver)
# Twisted requires us to define an 'application' attribute.
application = service.Application('Evennia')
# The main mud service. Import this for access to the server methods.
mud_service = EvenniaService()
mud_service.start_services(application)
if IMC2_ENABLED:
# IMC2 channel connections
from src.imc2.connection import IMC2ClientFactory
from src.imc2 import events as imc2_events
imc2_factory = IMC2ClientFactory()
svc = internet.TCPClient(settings.IMC2_SERVER_ADDRESS,
settings.IMC2_SERVER_PORT,
imc2_factory)
svc.setName('IMC2')
EVENNIA.services.addService(svc)
imc2_events.add_events()
if IRC_ENABLED:
# IRC channel connections
from src.irc.connection import IRC_BotFactory
irc = internet.TCPClient(settings.IRC_NETWORK,
settings.IRC_PORT,
IRC_BotFactory(settings.IRC_CHANNEL,
settings.IRC_NETWORK,
settings.IRC_NICKNAME))
irc.setName("%s:%s" % ("IRC", settings.IRC_CHANNEL))
EVENNIA.services.addService(irc)