mirror of
https://github.com/evennia/evennia.git
synced 2026-03-26 17:56:32 +01:00
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:
parent
ecefbfac01
commit
251f94aa7a
118 changed files with 9049 additions and 593 deletions
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue