Skip to content

Commit 34ba7e6

Browse files
author
Snowy
committed
Astron: Add Astron files
1 parent e9a9ad0 commit 34ba7e6

File tree

4 files changed

+1445
-0
lines changed

4 files changed

+1445
-0
lines changed
Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
"""AstronClientRepository module: contains the AstronClientRepository class"""
2+
3+
from direct.directnotify import DirectNotifyGlobal
4+
from .ClientRepositoryBase import ClientRepositoryBase
5+
from .MsgTypes import *
6+
from direct.distributed.PyDatagram import PyDatagram
7+
from panda3d.direct import STUint16, STUint32
8+
9+
class AstronClientRepository(ClientRepositoryBase):
10+
"""
11+
The Astron implementation of a clients repository for
12+
communication with an Astron ClientAgent.
13+
14+
This repo will emit events for:
15+
* CLIENT_HELLO_RESP
16+
* CLIENT_EJECT ( error_code, reason )
17+
* CLIENT_OBJECT_LEAVING ( do_id )
18+
* CLIENT_ADD_INTEREST ( context, interest_id, parent_id, zone_id )
19+
* CLIENT_ADD_INTEREST_MULTIPLE ( icontext, interest_id, parent_id, [zone_ids] )
20+
* CLIENT_REMOVE_INTEREST ( context, interest_id )
21+
* CLIENT_DONE_INTEREST_RESP ( context, interest_id )
22+
* LOST_CONNECTION ()
23+
"""
24+
25+
notify = DirectNotifyGlobal.directNotify.newCategory("ClientRepository")
26+
27+
# This is required by DoCollectionManager, even though it's not
28+
# used by this implementation.
29+
GameGlobalsId = 0
30+
31+
def __init__(self, *args, **kwargs):
32+
ClientRepositoryBase.__init__(self, *args, **kwargs)
33+
base.finalExitCallbacks.append(self.shutdown)
34+
self.message_handlers = {CLIENT_HELLO_RESP: self.handleHelloResp,
35+
CLIENT_EJECT: self.handleEject,
36+
CLIENT_ENTER_OBJECT_REQUIRED: self.handleEnterObjectRequired,
37+
CLIENT_ENTER_OBJECT_REQUIRED_OWNER: self.handleEnterObjectRequiredOwner,
38+
CLIENT_OBJECT_SET_FIELD: self.handleUpdateField,
39+
CLIENT_OBJECT_SET_FIELDS: self.handleUpdateFields,
40+
CLIENT_OBJECT_LEAVING: self.handleObjectLeaving,
41+
CLIENT_OBJECT_LOCATION: self.handleObjectLocation,
42+
CLIENT_ADD_INTEREST: self.handleAddInterest,
43+
CLIENT_ADD_INTEREST_MULTIPLE: self.handleAddInterestMultiple,
44+
CLIENT_REMOVE_INTEREST: self.handleRemoveInterest,
45+
CLIENT_DONE_INTEREST_RESP: self.handleInterestDoneMessage,
46+
}
47+
48+
#
49+
# Message Handling
50+
#
51+
52+
def handleDatagram(self, di):
53+
msgType = self.getMsgType()
54+
# self.handleMessageType(msgType, di)
55+
#
56+
#def handleMessageType(self, msgType, di):
57+
if msgType in self.message_handlers:
58+
self.message_handlers[msgType](di)
59+
else:
60+
self.notify.error("Got unknown message type %d!" % (msgType,))
61+
62+
self.considerHeartbeat()
63+
64+
def handleHelloResp(self, di):
65+
messenger.send("CLIENT_HELLO_RESP", [])
66+
67+
def handleEject(self, di):
68+
error_code = di.get_uint16()
69+
reason = di.get_string()
70+
messenger.send("CLIENT_EJECT", [error_code, reason])
71+
72+
def handleEnterObjectRequired(self, di):
73+
do_id = di.getArg(STUint32)
74+
parent_id = di.getArg(STUint32)
75+
zone_id = di.getArg(STUint32)
76+
dclass_id = di.getArg(STUint16)
77+
dclass = self.dclassesByNumber[dclass_id]
78+
self.generateWithRequiredFields(dclass, do_id, di, parent_id, zone_id)
79+
80+
def handleEnterObjectRequiredOwner(self, di):
81+
avatar_doId = di.getArg(STUint32)
82+
parentId = di.getArg(STUint32)
83+
zoneId = di.getArg(STUint32)
84+
dclass_id = di.getArg(STUint16)
85+
dclass = self.dclassesByNumber[dclass_id]
86+
self.generateWithRequiredFieldsOwner(dclass, avatar_doId, di)
87+
88+
def generateWithRequiredFieldsOwner(self, dclass, doId, di):
89+
if doId in self.doId2ownerView:
90+
# ...it is in our dictionary.
91+
# Just update it.
92+
self.notify.error('duplicate owner generate for %s (%s)' % (
93+
doId, dclass.getName()))
94+
distObj = self.doId2ownerView[doId]
95+
assert distObj.dclass == dclass
96+
distObj.generate()
97+
distObj.updateRequiredFields(dclass, di)
98+
# updateRequiredFields calls announceGenerate
99+
elif self.cacheOwner.contains(doId):
100+
# ...it is in the cache.
101+
# Pull it out of the cache:
102+
distObj = self.cacheOwner.retrieve(doId)
103+
assert distObj.dclass == dclass
104+
# put it in the dictionary:
105+
self.doId2ownerView[doId] = distObj
106+
# and update it.
107+
distObj.generate()
108+
distObj.updateRequiredFields(dclass, di)
109+
# updateRequiredFields calls announceGenerate
110+
else:
111+
# ...it is not in the dictionary or the cache.
112+
# Construct a new one
113+
classDef = dclass.getOwnerClassDef()
114+
if classDef == None:
115+
self.notify.error("Could not create an undefined %s object. Have you created an owner view?" % (dclass.getName()))
116+
distObj = classDef(self)
117+
distObj.dclass = dclass
118+
# Assign it an Id
119+
distObj.doId = doId
120+
# Put the new do in the dictionary
121+
self.doId2ownerView[doId] = distObj
122+
# Update the required fields
123+
distObj.generateInit() # Only called when constructed
124+
distObj.generate()
125+
distObj.updateRequiredFields(dclass, di)
126+
# updateRequiredFields calls announceGenerate
127+
return distObj
128+
129+
def handleUpdateFields(self, di):
130+
# Can't test this without the server actually sending it.
131+
self.notify.error("CLIENT_OBJECT_SET_FIELDS not implemented!")
132+
# # Here's some tentative code and notes:
133+
# do_id = di.getUint32()
134+
# field_count = di.getUint16()
135+
# for i in range(0, field_count):
136+
# field_id = di.getUint16()
137+
# field = self.get_dc_file().get_field_by_index(field_id)
138+
# # print(type(field))
139+
# # print(field)
140+
# # FIXME: Get field type, unpack value, create and send message.
141+
# # value = di.get?()
142+
# # Assemble new message
143+
144+
def handleObjectLeaving(self, di):
145+
do_id = di.get_uint32()
146+
dist_obj = self.doId2do.get(do_id)
147+
dist_obj.delete()
148+
self.deleteObject(do_id)
149+
messenger.send("CLIENT_OBJECT_LEAVING", [do_id])
150+
151+
def handleAddInterest(self, di):
152+
context = di.get_uint32()
153+
interest_id = di.get_uint16()
154+
parent_id = di.get_uint32()
155+
zone_id = di.get_uint32()
156+
messenger.send("CLIENT_ADD_INTEREST", [context, interest_id, parent_id, zone_id])
157+
158+
def handleAddInterestMultiple(self, di):
159+
context = di.get_uint32()
160+
interest_id = di.get_uint16()
161+
parent_id = di.get_uint32()
162+
zone_ids = [di.get_uint32() for i in range(0, di.get_uint16())]
163+
messenger.send("CLIENT_ADD_INTEREST_MULTIPLE", [context, interest_id, parent_id, zone_ids])
164+
165+
def handleRemoveInterest(self, di):
166+
context = di.get_uint32()
167+
interest_id = di.get_uint16()
168+
messenger.send("CLIENT_REMOVE_INTEREST", [context, interest_id])
169+
170+
def deleteObject(self, doId):
171+
"""
172+
implementation copied from ClientRepository.py
173+
174+
Removes the object from the client's view of the world. This
175+
should normally not be called directly except in the case of
176+
error recovery, since the server will normally be responsible
177+
for deleting and disabling objects as they go out of scope.
178+
179+
After this is called, future updates by server on this object
180+
will be ignored (with a warning message). The object will
181+
become valid again the next time the server sends a generate
182+
message for this doId.
183+
184+
This is not a distributed message and does not delete the
185+
object on the server or on any other client.
186+
"""
187+
if doId in self.doId2do:
188+
# If it is in the dictionary, remove it.
189+
obj = self.doId2do[doId]
190+
# Remove it from the dictionary
191+
del self.doId2do[doId]
192+
# Disable, announce, and delete the object itself...
193+
# unless delayDelete is on...
194+
obj.deleteOrDelay()
195+
if self.isLocalId(doId):
196+
self.freeDoId(doId)
197+
elif self.cache.contains(doId):
198+
# If it is in the cache, remove it.
199+
self.cache.delete(doId)
200+
if self.isLocalId(doId):
201+
self.freeDoId(doId)
202+
else:
203+
# Otherwise, ignore it
204+
self.notify.warning(
205+
"Asked to delete non-existent DistObj " + str(doId))
206+
207+
#
208+
# Sending messages
209+
#
210+
211+
def sendUpdate(self, distObj, fieldName, args):
212+
""" Sends a normal update for a single field. """
213+
dg = distObj.dclass.clientFormatUpdate(
214+
fieldName, distObj.doId, args)
215+
self.send(dg)
216+
217+
# FIXME: The version string should default to a .prc variable.
218+
def sendHello(self, version_string):
219+
dg = PyDatagram()
220+
dg.add_uint16(CLIENT_HELLO)
221+
dg.add_uint32(self.get_dc_file().get_hash())
222+
dg.add_string(version_string)
223+
self.send(dg)
224+
225+
def sendHeartbeat(self):
226+
datagram = PyDatagram()
227+
datagram.addUint16(CLIENT_HEARTBEAT)
228+
self.send(datagram)
229+
230+
def sendAddInterest(self, context, interest_id, parent_id, zone_id):
231+
dg = PyDatagram()
232+
dg.add_uint16(CLIENT_ADD_INTEREST)
233+
dg.add_uint32(context)
234+
dg.add_uint16(interest_id)
235+
dg.add_uint32(parent_id)
236+
dg.add_uint32(zone_id)
237+
self.send(dg)
238+
239+
def sendAddInterestMultiple(self, context, interest_id, parent_id, zone_ids):
240+
dg = PyDatagram()
241+
dg.add_uint16(CLIENT_ADD_INTEREST_MULTIPLE)
242+
dg.add_uint32(context)
243+
dg.add_uint16(interest_id)
244+
dg.add_uint32(parent_id)
245+
dg.add_uint16(len(zone_ids))
246+
for zone_id in zone_ids:
247+
dg.add_uint32(zone_id)
248+
self.send(dg)
249+
250+
def sendRemoveInterest(self, context, interest_id):
251+
dg = PyDatagram()
252+
dg.add_uint16(CLIENT_REMOVE_INTEREST)
253+
dg.add_uint32(context)
254+
dg.add_uint16(interest_id)
255+
self.send(dg)
256+
257+
#
258+
# Other stuff
259+
#
260+
261+
def lostConnection(self):
262+
messenger.send("LOST_CONNECTION")
263+
264+
def disconnect(self):
265+
"""
266+
This implicitly deletes all objects from the repository.
267+
"""
268+
for do_id in list(self.doId2do.keys()):
269+
self.deleteObject(do_id)
270+
ClientRepositoryBase.disconnect(self)

0 commit comments

Comments
 (0)