From 61e15cff47c90b00be450b7640ab7225f1290f0a Mon Sep 17 00:00:00 2001 From: Jaculabilis Date: Mon, 2 Oct 2017 17:19:09 -0500 Subject: [PATCH] Add framework for entity serialization --- Jormungand.py | 58 ++++++++++++++ src/__init__.py => __init__.py | 0 assets/ball64.png | Bin 0 -> 632 bytes assets/tube.png | Bin 0 -> 4138 bytes entity/Entity.py | 83 ++++++++++++++++++++ entity/Tube.py | 32 ++++++++ {src/entity => entity}/__init__.py | 0 entity/dummy/DebugBall.py | 62 +++++++++++++++ {src/entity => entity}/dummy/DebugJumper.py | 82 +++++++++---------- {src/entity => entity}/dummy/__init__.py | 0 src/Fishtank.py | 43 ---------- src/entity/Entity.py | 25 ------ src/entity/Tube.py | 22 ------ src/entity/dummy/DebugWaiter.py | 19 ----- src/network/NetworkGate.py | 19 ----- src/server.py | 54 ------------- util/Util.py | 26 ++++++ {src/network => util}/__init__.py | 0 world/Fishtank.py | 71 +++++++++++++++++ world/NetworkGate.py | 21 +++++ world/Server.py | 54 +++++++++++++ world/__init__.py | 0 22 files changed, 448 insertions(+), 223 deletions(-) create mode 100644 Jormungand.py rename src/__init__.py => __init__.py (100%) create mode 100644 assets/ball64.png create mode 100644 assets/tube.png create mode 100644 entity/Entity.py create mode 100644 entity/Tube.py rename {src/entity => entity}/__init__.py (100%) create mode 100644 entity/dummy/DebugBall.py rename {src/entity => entity}/dummy/DebugJumper.py (53%) rename {src/entity => entity}/dummy/__init__.py (100%) delete mode 100644 src/Fishtank.py delete mode 100644 src/entity/Entity.py delete mode 100644 src/entity/Tube.py delete mode 100644 src/entity/dummy/DebugWaiter.py delete mode 100644 src/network/NetworkGate.py delete mode 100644 src/server.py create mode 100644 util/Util.py rename {src/network => util}/__init__.py (100%) create mode 100644 world/Fishtank.py create mode 100644 world/NetworkGate.py create mode 100644 world/Server.py create mode 100644 world/__init__.py diff --git a/Jormungand.py b/Jormungand.py new file mode 100644 index 0000000..b43824f --- /dev/null +++ b/Jormungand.py @@ -0,0 +1,58 @@ +import sys +from collections import defaultdict +from util.Util import logout + +class Jormungand(): + def __init__(self, argv): + command = argv[1] if len(argv) >= 2 else "help" + registry = defaultdict(lambda: Jormungand.help, { + "help": Jormungand.help, + "run": Jormungand.run, + "send": Jormungand.send + }) + exec_func = registry[command] + self.execute = lambda: exec_func(argv) + + @staticmethod + def help(argv): + print("Hello, world!") + + @staticmethod + def run(argv): + if len(argv) < 3: + print("run requires a port") + sys.exit(-1) + port = int(argv[2]) + from world.Server import Server + server = Server(port) + logout("Launching server on port {}".format(port), "Jormungand run") + server.run(argv[3:]) + + @staticmethod + def send(argv): + if len(argv) < 4: + print("send requires a target and payload") + sys.exit(-1) + address = argv[2] + import zmq, json, pygame + pygame.display.set_mode((1,1), pygame.NOFRAME) + context = zmq.Context() + socket = context.socket(zmq.REQ) + socket.connect("tcp://{}".format(address)) + for i in range(3, len(argv)): + if argv[i] == "-ball": + from entity.dummy.DebugBall import DebugBall + e = DebugBall() + s = json.dumps(e.serialize()) + print("Sending {}".format(str(e))) + for i in range(100): + socket.send_string(s) + message = socket.recv() + print("Reply: {}".format(message)) + +def main(): + j = Jormungand(sys.argv) + j.execute() + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/src/__init__.py b/__init__.py similarity index 100% rename from src/__init__.py rename to __init__.py diff --git a/assets/ball64.png b/assets/ball64.png new file mode 100644 index 0000000000000000000000000000000000000000..60d11b5e36498a7fd2d916a37c8ef06ee24ca30b GIT binary patch literal 632 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 literal 0 HcmV?d00001 diff --git a/entity/Entity.py b/entity/Entity.py new file mode 100644 index 0000000..2f726a3 --- /dev/null +++ b/entity/Entity.py @@ -0,0 +1,83 @@ +import sys +from random import randrange + +class Entity(object): + """ + An Entity is something that exists in the Fishtank entities list. The Entity class provides + some basic structure to the behavior of entities, including position and velocity, + serialization, and update and draw. Each entity has a random 8-digit hex id for identification + purposes. + """ + def __init__(self): + self.fishtank = None + self.id = randrange(16**8) + self.x = 0 + self.y = 0 + self.z = 0 + self.vx = 0 + self.vy = 0 + + def __repr__(self): + return "[{}#{:8x} p{},{} z{} v{},{}]".format(type(self).__name__, + self.id, self.x, self.y, self.z, self.vx, self.vy) + + def __str__(self): + return "[{}#{:8x}]".format(type(self).__name__, self.id) + + def serialize(self): + """ + Returns a JSON-compatbible representation of the current state of this entity. + Subclasses should override this method, call it from their superclass, then update the + returned representation with its own information, including the subclass's module and + class and any idiomatic fields of that class. + Output: serialized representation of this entity + """ + return { + "module":"entity.Entity", + "class":"Entity", + "id":"{:8x}".format(self.id), + "p":[self.x,self.y], + "z":self.z, + "v":[self.vx,self.vy] + } + + @staticmethod + def deserialize(serial): + """ + Reconstructs an Entity from a serialized representation as returned by Entity.serialize(). + Subclasses should reimplement this method with e as their own class. + Input: serial, a serialized representation of an entity + Output: an entity reproducing the state represented in serial + """ + e = Entity() + return Entity.rebuild(e, serial) + + @staticmethod + def rebuild(e, serial): + """ + Helper function for Entity.deserialize(). + Subclasses should override this method, call it from their superclass, then add + deserialization for their idiomatic fields. + Input: e, a newly initialized entity + serial, a serialized representation of an entity as passed to deserialize() + Output: an entity reproducing the state represented in serial + """ + e.id = int(serial['id'], 16) + e.x, e.y = serial['p'] + e.z = serial['z'] + e.vx, e.vy = serial['v'] + return e + + def update(self, delta): + """ + Updates this entity during the update pass of the game loop. + Input: delta, the number of seconds since the last tick + """ + pass + + def draw(self, screen): + """ + Draws this entity during the draw pass of the game loop. + Input: screen, a Surface object to draw this entity on. + """ + pass \ No newline at end of file diff --git a/entity/Tube.py b/entity/Tube.py new file mode 100644 index 0000000..f67e388 --- /dev/null +++ b/entity/Tube.py @@ -0,0 +1,32 @@ +import sys +from entity.Entity import Entity +from util.Util import logout, load_image + +class Tube(Entity): + def __init__(self, network_gate): + Entity.__init__(self) + self.gate = network_gate + self.inbox = [] + self.texture = load_image("tube.png") + self.x, self.y = 200, 200 + self.z = -1 + + def __repr__(self): + return "[Tube gate={} inbox={}]".format(repr(self.gate), repr(self.inbox)) + + def accept(self, entity): + self.fishtank.remove_entity(entity) + self.inbox.append(entity) + logout("Accepted: {}".format(str(entity), "Tube#{}".format(self.id))) + + def update(self, delta): + Entity.update(self, delta) + if self.inbox: + entity = self.inbox.pop(0) + self.gate.transmit(entity.serialize()) + + def draw(self, screen): + Entity.draw(self, screen) + rect = self.texture.get_rect() + rect.center = (int(self.x), int(self.y)) + screen.blit(self.texture, rect) \ No newline at end of file diff --git a/src/entity/__init__.py b/entity/__init__.py similarity index 100% rename from src/entity/__init__.py rename to entity/__init__.py diff --git a/entity/dummy/DebugBall.py b/entity/dummy/DebugBall.py new file mode 100644 index 0000000..6324b7e --- /dev/null +++ b/entity/dummy/DebugBall.py @@ -0,0 +1,62 @@ +import sys +import random +import pygame +from entity.Entity import Entity +from entity.Tube import Tube +from util.Util import load_image + +class DebugBall(Entity): + """ + A debug entity that bounces around and jumps through network tubes. + """ + def __init__(self): + Entity.__init__(self) + self.vx = 32 + self.vy = 128 + self.dummy = "DUMMY" + self.texture = load_image("ball64.png") + self.texture = pygame.transform.smoothscale(self.texture, (16,16)) + self.rect = pygame.Rect(0, 0, 16, 16) + + def serialize(self): + e = Entity.serialize(self) + e.update({ + "module":"entity.dummy.DebugBall", + "class":"DebugBall", + "dummy":self.dummy + }) + return e + + @staticmethod + def deserialize(serial): + e = DebugBall() + return DebugBall.rebuild(e, serial) + + @staticmethod + def rebuild(e, serial): + Entity.rebuild(e, serial) + e.dummy = serial["dummy"] + return e + + def update(self, delta): + Entity.update(self, delta) + self.x += self.vx * delta + self.y += self.vy * delta + if self.x < 0 or self.x > self.fishtank.size[0]: + self.vx = -self.vx + self.x = 0 if self.x < 0 else self.fishtank.size[0] if self.x > self.fishtank.size[0] else self.x + if self.y < 0 or self.y > self.fishtank.size[1]: + self.vy = -self.vy + self.y = 0 if self.y < 0 else self.fishtank.size[1] if self.y > self.fishtank.size[1] else self.y + + for entity in self.fishtank.entities: + if type(entity) is Tube and abs(self.x - entity.x) < 64 and abs(self.y - entity.y) < 64: + entity.accept(self) + break + + def draw(self, screen): + Entity.draw(self, screen) + #rect = self.texture.get_rect() + self.rect.center = (int(self.x), int(self.y)) + screen.blit(self.texture, self.rect) + \ No newline at end of file diff --git a/src/entity/dummy/DebugJumper.py b/entity/dummy/DebugJumper.py similarity index 53% rename from src/entity/dummy/DebugJumper.py rename to entity/dummy/DebugJumper.py index 7f2dafd..f2590bd 100644 --- a/src/entity/dummy/DebugJumper.py +++ b/entity/dummy/DebugJumper.py @@ -1,41 +1,41 @@ -import sys -from entity.Entity import Entity -from entity.Tube import Tube - -class DebugJumper(Entity): - def __init__(self, message): - Entity.__init__(self) - self._message = message - self._counter = 0 - - def __repr__(self): - return "[DebugJumper message='{}' counter={}]".format(self._message, self._counter) - - def serialize(self): - sup = Entity.serialize(self) - sup.update({ - "module":"entity.dummy.DebugJumper", - "class":"DebugJumper", - "message":self._message, - "counter":self._counter - }) - return sup - - @staticmethod - def deserialize(serial): - e = DebugJumper(serial["message"]) - e._counter = serial["counter"] - return e - - def update(self): - Entity.update(self) - self._counter += 1 - if self._counter % 5 == 0: - for entity in self._fishtank._entities: - if type(entity) is Tube: - entity.accept(self) - break - - def draw(self): - Entity.draw(self) - sys.stdout.write(self._message + " ({})\n".format(self._counter)) \ No newline at end of file +import sys +from entity.Entity import Entity +from entity.Tube import Tube + +class DebugJumper(Entity): + def __init__(self, message): + Entity.__init__(self) + self.message = message + self.counter = 0 + + def __repr__(self): + return "[DebugJumper @{},{} message='{}' counter={}]".format(self.x, self.y, self.message, self.counter) + + def serialize(self): + sup = Entity.serialize(self) + sup.update({ + "module":"entity.dummy.DebugJumper", + "class":"DebugJumper", + "message":self.message, + "counter":self.counter + }) + return sup + + @staticmethod + def deserialize(serial): + e = DebugJumper(serial["message"]) + e.counter = serial["counter"] + return e + + def update(self, delta): + Entity.update(self) + self.counter += 1 + if self.counter % 5 == 0: + for entity in self.fishtank.entities: + if type(entity) is Tube: + entity.accept(self) + break + + def draw(self, screen): + Entity.draw(self) + sys.stdout.write(self.message + " ({})\n".format(self.counter)) \ No newline at end of file diff --git a/src/entity/dummy/__init__.py b/entity/dummy/__init__.py similarity index 100% rename from src/entity/dummy/__init__.py rename to entity/dummy/__init__.py diff --git a/src/Fishtank.py b/src/Fishtank.py deleted file mode 100644 index 7ca2a5c..0000000 --- a/src/Fishtank.py +++ /dev/null @@ -1,43 +0,0 @@ -import sys -import time -import json - -class Fishtank(): - def __init__(self, recv_queue): - self._entities = [] - self._to_remove = [] - self._recv_queue = recv_queue - - def run(self): - while True: - # Delete flagged entities - if self._to_remove: - for entity in self._to_remove: - self._entities.remove(entity) - self._to_remove = [] - # Update and draw - for entity in self._entities: - entity.update() - for entity in self._entities: - entity.draw() - #time.sleep(1) - # Intake queue - if not self._recv_queue.empty(): - serial = self._recv_queue.get(False) - sys.stdout.write("Fishtank dequeued a {}\n".format(serial["class"])) - mod = __import__(serial["module"], fromlist=[serial["class"]]) - klass = getattr(mod, serial["class"]) - e = klass.deserialize(serial) - self.add_entity(e) - - def add_entity(self, entity): - entity._fishtank = self - self._entities.append(entity) - sys.stdout.write("Added: {}\n".format(repr(entity))) - - def remove_entity(self, entity): - if entity not in self._entities: - sys.stderr.write( - "WARN: remove called for entity '{}', but it isn't in the eneityt list\n".format(entity.__name__)) - return - self._to_remove.append(entity) \ No newline at end of file diff --git a/src/entity/Entity.py b/src/entity/Entity.py deleted file mode 100644 index bc7ad90..0000000 --- a/src/entity/Entity.py +++ /dev/null @@ -1,25 +0,0 @@ -import sys - -class Entity(object): - def __init__(self): - pass - - def __repr__(self): - return "[Entity]" - - def serialize(self): - return { - "module":"entity.Entity", - "class":"Entity" - } - - @staticmethod - def deserialize(serial): - e = Entity() - return e - - def update(self): - pass - - def draw(self): - pass \ No newline at end of file diff --git a/src/entity/Tube.py b/src/entity/Tube.py deleted file mode 100644 index 50af4d4..0000000 --- a/src/entity/Tube.py +++ /dev/null @@ -1,22 +0,0 @@ -import sys -from entity.Entity import Entity - -class Tube(Entity): - def __init__(self, network_gate): - Entity.__init__(self) - self._gate = network_gate - self._inbox = [] - - def __repr__(self): - return "[Tube gate={} inbox={}]".format(repr(self._gate), repr(self._inbox)) - - def accept(self, entity): - self._fishtank.remove_entity(entity) - self._inbox.append(entity) - - def update(self): - Entity.update(self) - if self._inbox: - entity = self._inbox.pop(0) - sys.stdout.write("Sending: {}\n".format(repr(entity))) - self._gate.transmit(entity.serialize()) \ No newline at end of file diff --git a/src/entity/dummy/DebugWaiter.py b/src/entity/dummy/DebugWaiter.py deleted file mode 100644 index 743610f..0000000 --- a/src/entity/dummy/DebugWaiter.py +++ /dev/null @@ -1,19 +0,0 @@ -import sys -from entity.Entity import Entity - -class DebugWaiter(Entity): - def __init__(self, message): - Entity.__init__(self) - self._message = message - self._counter = 0 - - def __repr__(self): - return "[DebugWaiter message='{}' counter={}]".format(self._message, self._counter) - - def update(self): - Entity.update(self) - self._counter += 1 - - def draw(self): - Entity.draw(self) - sys.stdout.write(self._message + " ({})\n".format(self._counter)) \ No newline at end of file diff --git a/src/network/NetworkGate.py b/src/network/NetworkGate.py deleted file mode 100644 index 8d8ccf4..0000000 --- a/src/network/NetworkGate.py +++ /dev/null @@ -1,19 +0,0 @@ -import sys -import zmq -import json - -class NetworkGate(): - def __init__(self, address, port): - self._address = address - self._port = port - context = zmq.Context() - self._socket = context.socket(zmq.REQ) - self._socket.connect("tcp://{}:{}".format(address, port)) - - def __repr__(self): - return "[NetworkGate {}:{}]".format(self._address, self._port) - - def transmit(self, serial): - s = json.dumps(serial) - self._socket.send_string(s) - message = self._socket.recv() \ No newline at end of file diff --git a/src/server.py b/src/server.py deleted file mode 100644 index 7b4d45e..0000000 --- a/src/server.py +++ /dev/null @@ -1,54 +0,0 @@ -import sys -import zmq -import json -import time -from multiprocessing import Process, Queue -from Fishtank import Fishtank - -def socket_listener(port, recv_queue): - sys.stdout.write("Socket listener starting...\n") - context = zmq.Context() - socket = context.socket(zmq.REP) - socket.bind("tcp://*:{}".format(port)) - - while True: - message = socket.recv() - response = b"Undefined response" - try: - serial = json.loads(message) - if "class" in serial: - sys.stdout.write("Listener received a {}\n".format(serial["class"])) - recv_queue.put(serial, False) - response = b"Received" - except: - response = b"Error" - socket.send(response) - -def main(): - port = int(sys.argv[1]) - sys.stdout.write("Launching on port {}\n".format(port)) - # Spawn the socket thread - q = Queue() - socket_proc = Process(target=socket_listener, args=(port,q)) - socket_proc.start() - sys.stdout.write("Socket thread started\n") - time.sleep(1) - # Build the world - fishtank = Fishtank(q) - for i in range(1, len(sys.argv)): - if sys.argv[i] == "-w": - from entity.dummy.DebugWaiter import DebugWaiter - fishtank.add_entity(DebugWaiter("DebugWaiter")) - if sys.argv[i] == "-j": - from entity.dummy.DebugJumper import DebugJumper - fishtank.add_entity(DebugJumper("DebugJumper")) - if sys.argv[i] == "-t": - pipe_port = int(sys.argv[i+1]) - from network.NetworkGate import NetworkGate - network_gate = NetworkGate("localhost", pipe_port) - from entity.Tube import Tube - fishtank.add_entity(Tube(network_gate)) - fishtank.run() - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/util/Util.py b/util/Util.py new file mode 100644 index 0000000..54fdf3e --- /dev/null +++ b/util/Util.py @@ -0,0 +1,26 @@ +import datetime +from os.path import join +import sys +import pygame + +def logout(s, tag=""): + sys.stdout.write("[{:%Y-%m-%d %H:%M:%S} {}] {}\n".format(datetime.datetime.now(), tag, s)) + +def logerr(s, tag=""): + sys.stderr.write("[{:%Y-%m-%d %H:%M:%S} {}] {}\n".format(datetime.datetime.now(), tag, s)) + +def load_image(filename): + path = join("assets", filename) + try: + image = pygame.image.load(path) + except: + logerr("ERROR: Image '{}' not found".format(filename), "load_image") + sys.exit(-1) + #try: + if image.get_alpha() is None: + image = image.convert() + else: + image = image.convert_alpha() + #except: + # logerr("ERROR: Image '{}' not converted".format(filename)) + return image \ No newline at end of file diff --git a/src/network/__init__.py b/util/__init__.py similarity index 100% rename from src/network/__init__.py rename to util/__init__.py diff --git a/world/Fishtank.py b/world/Fishtank.py new file mode 100644 index 0000000..73a0381 --- /dev/null +++ b/world/Fishtank.py @@ -0,0 +1,71 @@ +import sys +import time +import json +import pygame +from util.Util import logout, logerr + +class Fishtank(): + def __init__(self, recv_queue): + self.entities = [] + self.to_remove = [] + self.recv_queue = recv_queue + + self.size = (480, 360) + self.screen = pygame.display.set_mode(self.size) + + def run(self): + """Begins the game loop. Does not return.""" + clock = pygame.time.Clock() + + while True: + # Upkeep + milli = clock.tick(60) / 1000 + + for event in pygame.event.get(): + if event.type == pygame.QUIT: + sys.exit(0) + + # Delete flagged entities + if self.to_remove: + for entity in self.to_remove: + self.entities.remove(entity) + self.to_remove = [] + + # Update + for entity in self.entities: + entity.update(milli) + + # Intake queue + if not self.recv_queue.empty(): + serial = self.recv_queue.get(False) + logout("Dequeued: {}".format(serial["class"]), "Fishtank") + mod = __import__(serial["module"], fromlist=[serial["class"]]) + klass = getattr(mod, serial["class"]) + e = klass.deserialize(serial) + self.add_entity(e) + + # Draw + self.screen.fill((100, 149, 237)) + for entity in sorted(self.entities, key=lambda e:e.z): + entity.draw(self.screen) + pygame.display.flip() + + def add_entity(self, entity): + """ + Adds an entity to the entity list and sets its fishtank property to this. + Input: entity, the entity to add + """ + entity.fishtank = self + self.entities.append(entity) + logout("Added: {}".format(repr(entity)), "Fishtank") + + def remove_entity(self, entity): + """ + Marks an entity to be removed before the next update pass. + Input: entity, the entity to remove + """ + if entity not in self.entities: + logerr("WARN: remove called for entity '{}',"\ + "but it isn't in the entity list".format(entity.__name__), "Fishtank") + return + self.to_remove.append(entity) \ No newline at end of file diff --git a/world/NetworkGate.py b/world/NetworkGate.py new file mode 100644 index 0000000..3b1cd8d --- /dev/null +++ b/world/NetworkGate.py @@ -0,0 +1,21 @@ +import sys +import zmq +import json +from util.Util import logout + +class NetworkGate(): + def __init__(self, address, port): + self.address = address + self.port = port + context = zmq.Context() + self.socket = context.socket(zmq.REQ) + self.socket.connect("tcp://{}:{}".format(address, port)) + + def __repr__(self): + return "[NetworkGate {}:{}]".format(self.address, self.port) + + def transmit(self, serial): + s = json.dumps(serial) + logout("Sending: {}".format(serial["class"])) + self.socket.send_string(s) + message = self.socket.recv() \ No newline at end of file diff --git a/world/Server.py b/world/Server.py new file mode 100644 index 0000000..103e0a5 --- /dev/null +++ b/world/Server.py @@ -0,0 +1,54 @@ +import zmq +import json +import time +from multiprocessing import Process, Queue +from world.Fishtank import Fishtank +from util.Util import logout + +class Server(object): + def __init__(self, recv_port): + self.recv_port = recv_port + self.recv_queue = Queue() + self.recv_proc = Process( + target=self.socket_listener, + args=(self.recv_port, self.recv_queue)) + + def socket_listener(self, port, recv_queue): + logout("Socket thread starting", "Server.socket_listener") + context = zmq.Context() + socket = context.socket(zmq.REP) + socket.bind("tcp://*:{}".format(port)) + + while True: + message = socket.recv() + response = b"Undefined response" + try: + serial = json.loads(message) + if "class" in serial: + logout("Received: {}".format(serial["class"]), "Server.socket_listener") + recv_queue.put(serial, False) + response = b"Received" + except: + response = b"Error" + socket.send(response) + + def run(self, argv): + # Launch the recv process + self.recv_proc.start() + logout("Socket thread launched", "Server") + time.sleep(0.2) + + # Build the world + fishtank = Fishtank(self.recv_queue) + + for i in range(len(argv)): + if argv[i] == "-ball": + from entity.dummy.DebugBall import DebugBall + fishtank.add_entity(DebugBall()) + if argv[i] == "-tube": + tube_port = int(argv[i+1]) + from world.NetworkGate import NetworkGate + network_gate = NetworkGate("localhost", tube_port) + from entity.Tube import Tube + fishtank.add_entity(Tube(network_gate)) + fishtank.run() \ No newline at end of file diff --git a/world/__init__.py b/world/__init__.py new file mode 100644 index 0000000..e69de29