mirror of
https://github.com/Sneed-Group/Poodletooth-iLand
synced 2024-11-01 01:07:54 +00:00
276 lines
10 KiB
Python
276 lines
10 KiB
Python
|
from pandac.PandaModules import *
|
||
|
from MsgTypes import *
|
||
|
from direct.directnotify import DirectNotifyGlobal
|
||
|
from ConnectionRepository import ConnectionRepository
|
||
|
from PyDatagram import PyDatagram
|
||
|
from PyDatagramIterator import PyDatagramIterator
|
||
|
|
||
|
class AstronDatabaseInterface:
|
||
|
"""
|
||
|
This class is part of Panda3D's new MMO networking framework.
|
||
|
It interfaces with Astron database(s) to manage objects directly, rather than
|
||
|
via DB-StateServers.
|
||
|
|
||
|
Do not create this class directly; instead, use AstronInternalRepository's
|
||
|
dbInterface attribute.
|
||
|
"""
|
||
|
notify = DirectNotifyGlobal.directNotify.newCategory("AstronDatabaseInterface")
|
||
|
|
||
|
def __init__(self, air):
|
||
|
self.air = air
|
||
|
|
||
|
self._callbacks = {}
|
||
|
|
||
|
def createObject(self, databaseId, dclass, fields={}, callback=None):
|
||
|
"""
|
||
|
Create an object in the specified database.
|
||
|
|
||
|
databaseId specifies the control channel of the target database.
|
||
|
dclass specifies the class of the object to be created.
|
||
|
fields is a dict with any fields that should be stored in the object on creation.
|
||
|
callback will be called with callback(doId) if specified. On failure, doId is 0.
|
||
|
"""
|
||
|
|
||
|
# Save the callback:
|
||
|
ctx = self.air.getContext()
|
||
|
self._callbacks[ctx] = callback
|
||
|
|
||
|
# Pack up/count valid fields.
|
||
|
fieldPacker = DCPacker()
|
||
|
fieldCount = 0
|
||
|
for k,v in fields.items():
|
||
|
field = dclass.getFieldByName(k)
|
||
|
if not field:
|
||
|
self.notify.error('Creation request for %s object contains '
|
||
|
'invalid field named %s' % (dclass.getName(), k))
|
||
|
|
||
|
fieldPacker.rawPackUint16(field.getNumber())
|
||
|
fieldPacker.beginPack(field)
|
||
|
field.packArgs(fieldPacker, v)
|
||
|
fieldPacker.endPack()
|
||
|
fieldCount += 1
|
||
|
|
||
|
# Now generate and send the datagram:
|
||
|
dg = PyDatagram()
|
||
|
dg.addServerHeader(databaseId, self.air.ourChannel, DBSERVER_CREATE_OBJECT)
|
||
|
dg.addUint32(ctx)
|
||
|
dg.addUint16(dclass.getNumber())
|
||
|
dg.addUint16(fieldCount)
|
||
|
dg.appendData(fieldPacker.getString())
|
||
|
self.air.send(dg)
|
||
|
|
||
|
def handleCreateObjectResp(self, di):
|
||
|
ctx = di.getUint32()
|
||
|
doId = di.getUint32()
|
||
|
|
||
|
if ctx not in self._callbacks:
|
||
|
self.notify.warning('Received unexpected DBSERVER_CREATE_OBJECT_RESP'
|
||
|
' (ctx %d, doId %d)' % (ctx, doId))
|
||
|
return
|
||
|
|
||
|
if self._callbacks[ctx]:
|
||
|
self._callbacks[ctx](doId)
|
||
|
|
||
|
del self._callbacks[ctx]
|
||
|
|
||
|
def queryObject(self, databaseId, doId, callback):
|
||
|
"""
|
||
|
Query object `doId` out of the database.
|
||
|
|
||
|
On success, the callback will be invoked as callback(dclass, fields)
|
||
|
where dclass is a DCClass instance and fields is a dict.
|
||
|
On failure, the callback will be invoked as callback(None, None).
|
||
|
"""
|
||
|
|
||
|
# Save the callback:
|
||
|
ctx = self.air.getContext()
|
||
|
self._callbacks[ctx] = callback
|
||
|
|
||
|
# Generate and send the datagram:
|
||
|
dg = PyDatagram()
|
||
|
dg.addServerHeader(databaseId, self.air.ourChannel, DBSERVER_OBJECT_GET_ALL)
|
||
|
dg.addUint32(ctx)
|
||
|
dg.addUint32(doId)
|
||
|
self.air.send(dg)
|
||
|
|
||
|
def handleQueryObjectResp(self, di):
|
||
|
ctx = di.getUint32()
|
||
|
success = di.getUint8()
|
||
|
|
||
|
if ctx not in self._callbacks:
|
||
|
self.notify.warning('Received unexpected DBSERVER_OBJECT_GET_ALL_RESP'
|
||
|
' (ctx %d)' % (ctx))
|
||
|
return
|
||
|
|
||
|
try:
|
||
|
if not success:
|
||
|
if self._callbacks[ctx]:
|
||
|
self._callbacks[ctx](None, None)
|
||
|
return
|
||
|
|
||
|
dclassId = di.getUint16()
|
||
|
dclass = self.air.dclassesByNumber.get(dclassId)
|
||
|
|
||
|
if not dclass:
|
||
|
self.notify.error('Received bad dclass %d in'
|
||
|
' DBSERVER_OBJECT_GET_ALL_RESP' % (dclassId))
|
||
|
|
||
|
fieldCount = di.getUint16()
|
||
|
unpacker = DCPacker()
|
||
|
unpacker.setUnpackData(di.getRemainingBytes())
|
||
|
fields = {}
|
||
|
for x in xrange(fieldCount):
|
||
|
fieldId = unpacker.rawUnpackInt16()
|
||
|
field = dclass.getFieldByIndex(fieldId)
|
||
|
|
||
|
if not field:
|
||
|
self.notify.error('Received bad field %d in query for'
|
||
|
' %s object' % (fieldId, dclass.getName()))
|
||
|
|
||
|
|
||
|
unpacker.beginUnpack(field)
|
||
|
fields[field.getName()] = field.unpackArgs(unpacker)
|
||
|
unpacker.endUnpack()
|
||
|
|
||
|
if self._callbacks[ctx]:
|
||
|
self._callbacks[ctx](dclass, fields)
|
||
|
|
||
|
finally:
|
||
|
del self._callbacks[ctx]
|
||
|
|
||
|
def updateObject(self, databaseId, doId, dclass, newFields, oldFields=None, callback=None):
|
||
|
"""
|
||
|
Update field(s) on an object, optionally with the requirement that the
|
||
|
fields must match some old value.
|
||
|
|
||
|
databaseId and doId represent the database control channel and object ID
|
||
|
for the update request.
|
||
|
newFields is to be a dict of fieldname->value, representing the fields
|
||
|
to add/change on the database object.
|
||
|
oldFields, if specified, is a similarly-formatted dict that contains the
|
||
|
expected older values. If the values do not match, the database will
|
||
|
refuse to process the update. This is useful for guarding against race
|
||
|
conditions.
|
||
|
|
||
|
On success, the callback is called as callback(None).
|
||
|
On failure, the callback is called as callback(dict), where dict contains
|
||
|
the current object values. This is so that the updater can try again,
|
||
|
basing its updates off of the new values.
|
||
|
"""
|
||
|
|
||
|
# Ensure that the keys in newFields and oldFields are the same if
|
||
|
# oldFields is given...
|
||
|
if oldFields is not None:
|
||
|
if set(newFields.keys()) != set(oldFields.keys()):
|
||
|
self.notify.error('newFields and oldFields must contain the same keys!')
|
||
|
return
|
||
|
|
||
|
fieldPacker = DCPacker()
|
||
|
fieldCount = 0
|
||
|
for k,v in newFields.items():
|
||
|
field = dclass.getFieldByName(k)
|
||
|
if not field:
|
||
|
self.notify.error('Update for %s(%d) object contains invalid'
|
||
|
' field named %s' % (dclass.getName(), doId, k))
|
||
|
|
||
|
fieldPacker.rawPackUint16(field.getNumber())
|
||
|
|
||
|
if oldFields is not None:
|
||
|
# Pack the old values:
|
||
|
fieldPacker.beginPack(field)
|
||
|
field.packArgs(fieldPacker, oldFields[k])
|
||
|
fieldPacker.endPack()
|
||
|
|
||
|
fieldPacker.beginPack(field)
|
||
|
field.packArgs(fieldPacker, v)
|
||
|
fieldPacker.endPack()
|
||
|
|
||
|
fieldCount += 1
|
||
|
|
||
|
# Generate and send the datagram:
|
||
|
dg = PyDatagram()
|
||
|
if oldFields is not None:
|
||
|
ctx = self.air.getContext()
|
||
|
self._callbacks[ctx] = callback
|
||
|
if fieldCount == 1:
|
||
|
dg.addServerHeader(databaseId, self.air.ourChannel,
|
||
|
DBSERVER_OBJECT_SET_FIELD_IF_EQUALS)
|
||
|
else:
|
||
|
dg.addServerHeader(databaseId, self.air.ourChannel,
|
||
|
DBSERVER_OBJECT_SET_FIELDS_IF_EQUALS)
|
||
|
dg.addUint32(ctx)
|
||
|
else:
|
||
|
if fieldCount == 1:
|
||
|
dg.addServerHeader(databaseId, self.air.ourChannel,
|
||
|
DBSERVER_OBJECT_SET_FIELD)
|
||
|
else:
|
||
|
dg.addServerHeader(databaseId, self.air.ourChannel,
|
||
|
DBSERVER_OBJECT_SET_FIELDS)
|
||
|
dg.addUint32(doId)
|
||
|
if fieldCount != 1:
|
||
|
dg.addUint16(fieldCount)
|
||
|
dg.appendData(fieldPacker.getString())
|
||
|
self.air.send(dg)
|
||
|
|
||
|
if oldFields is None and callback is not None:
|
||
|
# Why oh why did they ask for a callback if there's no oldFields?
|
||
|
# Oh well, better honor their request:
|
||
|
callback(None)
|
||
|
|
||
|
def handleUpdateObjectResp(self, di, multi):
|
||
|
ctx = di.getUint32()
|
||
|
success = di.getUint8()
|
||
|
|
||
|
if ctx not in self._callbacks:
|
||
|
self.notify.warning('Received unexpected'
|
||
|
' DBSERVER_OBJECT_SET_FIELD(S)_IF_EQUALS_RESP'
|
||
|
' (ctx %d)' % (ctx))
|
||
|
return
|
||
|
|
||
|
try:
|
||
|
if success:
|
||
|
if self._callbacks[ctx]:
|
||
|
self._callbacks[ctx](None)
|
||
|
return
|
||
|
|
||
|
if not di.getRemainingSize():
|
||
|
# We failed due to other reasons.
|
||
|
if self._callbacks[ctx]:
|
||
|
return self._callbacks[ctx]({})
|
||
|
|
||
|
if multi:
|
||
|
fieldCount = di.getUint16()
|
||
|
else:
|
||
|
fieldCount = 1
|
||
|
|
||
|
unpacker = DCPacker()
|
||
|
unpacker.setUnpackData(di.getRemainingBytes())
|
||
|
fields = {}
|
||
|
for x in xrange(fieldCount):
|
||
|
fieldId = unpacker.rawUnpackInt16()
|
||
|
field = self.air.getDcFile().getFieldByIndex(fieldId)
|
||
|
|
||
|
if not field:
|
||
|
self.notify.error('Received bad field %d in update'
|
||
|
' failure response message' % (fieldId))
|
||
|
|
||
|
unpacker.beginUnpack(field)
|
||
|
fields[field.getName()] = field.unpackArgs(unpacker)
|
||
|
unpacker.endUnpack()
|
||
|
|
||
|
if self._callbacks[ctx]:
|
||
|
self._callbacks[ctx](fields)
|
||
|
|
||
|
finally:
|
||
|
del self._callbacks[ctx]
|
||
|
|
||
|
def handleDatagram(self, msgType, di):
|
||
|
if msgType == DBSERVER_CREATE_OBJECT_RESP:
|
||
|
self.handleCreateObjectResp(di)
|
||
|
elif msgType == DBSERVER_OBJECT_GET_ALL_RESP:
|
||
|
self.handleQueryObjectResp(di)
|
||
|
elif msgType == DBSERVER_OBJECT_SET_FIELD_IF_EQUALS_RESP:
|
||
|
self.handleUpdateObjectResp(di, False)
|
||
|
elif msgType == DBSERVER_OBJECT_SET_FIELDS_IF_EQUALS_RESP:
|
||
|
self.handleUpdateObjectResp(di, True)
|