class Network: hosting: bool = False def __init__(self): self.open() def get_all_groups(self) -> List[str]: """Get the names of groups that can be joined.""" groups = [] for peer in self.node.peers(): group = self.node.peer_header_value(peer, 'hosting') if group != None and len(group) > 0: groups.append(group) return groups def get_our_group(self) -> Union[str, None]: """What is the name of the group we're in or hosting?""" our_groups = self.node.own_groups() our_groups.remove('untangled2018') if len(our_groups) == 0: return None return our_groups[0] def is_in_group(self) -> bool: """Are we in or hosting a group?""" return self.get_our_group() != None def is_hosting(self) -> bool: """Are we hosting?""" return self.hosting def join_group(self, group: str) -> None: """Join a group of given name (assumes you are not in a group already).""" if group not in self.get_all_groups(): raise ValueError('Group named "%s" does not exist'.format(group)) if self.is_in_group(): raise ValueError( 'You must leave the previous group before you join another') self.node.join(group) def leave_group(self) -> None: """Leave any joined group or stop hosting.""" self.hosting = False if not self.is_in_group(): raise ValueError('You are not in a group') for group in self.node.own_groups(): self.node.leave(group) def host_group(self, name: str) -> None: """Host a group of given name.""" if name in self.get_all_groups(): raise ValueError('A group of the given name already exists') if self.is_in_group(): raise ValueError('Cannot host whilst in a group') self.node.set_header('hosting', name) self.hosting = True self.node.join(name) def open(self) -> None: """Create a new pyre instance and join untangled.""" self.node = Pyre() self.node.start() self.node.join('untangled2018') # used to get our messages self.poller = zmq.Poller() self.poller.register(self.node.socket(), zmq.POLLIN) def get_id(self) -> str: """Get our id, as a unique node on the network.""" return self.node.uuid() def is_me(self, player_id) -> bool: """See if a given id is ours.""" return self.get_id() == player_id def close(self) -> None: """Disconnect from everything""" self.node.stop() def get_messages(self): """See what has been sent to us: who has joined, what have people said, etc""" # what has changed changes = dict(self.poller.poll(0)) # are these the changes we subscribed for if self.node.socket() in changes and changes[ self.node.socket()] == zmq.POLLIN: msgs = self.node.recent_events() return msgs # nothing to return return [] def pull_game(self, game): """Update our game state based on what other people tell us.""" for msg in self.get_messages(): # is it relevant to us? if msg.group != self.get_our_group(): continue if msg.type == 'SHOUT': entities = bson.loads(msg.msg[0]) if 'ids' in entities: keys = entities['ids'] cur_keys = list(game.entities.keys()) diff = list(set(cur_keys) - set(keys)) for key in diff: del game.entities[key] entities = entities['components'] for key, changed_comps in entities.items(): key = uuid.UUID(key) if key not in game.entities: game.entities[key] = {} entity = game.entities[key] for compname, component in changed_comps.items(): try: clas = components.__dict__[compname] if clas in entity: entity[clas] = entity[clas].replace( **component) else: entity[clas] = clas(**component) entity[clas].observed_changes() except Exception: print( 'Error updating component, is everyone in the group on the same version?', file=sys.stdout) elif self.is_hosting(): if msg.type == 'JOIN': game.on_player_join(msg.peer_uuid) self.push_game(game, initial=True) elif msg.type == 'EXIT' or msg.type == "LEAVE": game.on_player_quit(msg.peer_uuid) def push_game(self, game, initial=False): """Tell others how we've changed the game state.""" if len(self.node.peers_by_group(self.get_our_group())) == 0: # Nobody else to talk to return entities = {'components': {}} if self.is_hosting(): entities = {'ids': [], 'components': {}} for key, entity in game.entities.items(): changed_comps = {} for component in entity.values(): if component.is_networked() and (initial or component.has_changed()): changed_comps[component.get_name()] = component.as_dict() component.observed_changes() if 'ids' in entities: entities['ids'].append(key) entities['components'][str(key)] = changed_comps self.node.shout(self.get_our_group(), bson.dumps(entities))
def network_manager(self, ctx, write_pipe, node_name, overlay_network_name, read_pipe): # create the poller to wait for messages from pipes and network poller = zmq.Poller() poller.register(write_pipe, zmq.POLLIN) # create the Pyre node object node = Pyre(node_name + str(uuid.uuid4())) # register node to the network, start it and register for events with # the poller. node.join(overlay_network_name) node.start() poller.register(node.socket(), zmq.POLLIN) while (True): # do stuff, aka wait and decode messages items = dict(poller.poll()) if write_pipe in items and items[write_pipe] == zmq.POLLIN: # here is where the thread receives internal data # check if we have to send something outside # or eventually die gracefully message = write_pipe.recv() # here I have a Command + a FardNetworkData object decoded_message = pickle.loads(message) command = decoded_message[0] network_data = decoded_message[1] if command == "$$STOP": # message to quit here break elif command == "$$GET_PEERS": # only synchronous command, retrieves the peers inside the # network of tasks. group = network_data.group peers = node.peers_by_group(group) list_of_peers = [] for peer in peers: list_of_peers.append(str(peer)) write_pipe.send(pickle.dumps(str(";".join(list_of_peers)))) elif command == "$$SEND_MESSAGE" in decoded_message: # send message to a single peer using Pyre # if requested, send back the message to the same node that # sent it. peer = network_data.peer network_data.sender_peer = str(node.uuid()) network_data.message_type = "peer" node.whisper(uuid.UUID(peer), pickle.dumps(network_data)) if network_data.auto_send and peer == network_data.sender_peer: read_pipe.send(pickle.dumps(network_data)) elif command == "$$SEND_TASK_MESSAGE": # send message to a group of identical tasks using Pyre. # Currently implemented with a shout that is ignored by a # receiver if the task name is different from his. # if requested, send back the message to the same node that # sent it. network_data.sender_peer = str(node.uuid()) network_data.message_type = "task" node.shout(group, pickle.dumps(network_data)) if network_data.auto_send: read_pipe.send(pickle.dumps(network_data)) elif command == "$$SEND_GROUP_MESSAGE": # send message to the whole application using Pyre # if requested, send back the message to the same node that # sent it. group = network_data.group network_data.sender_peer = str(node.uuid()) network_data.message_type = "group" node.shout(group, pickle.dumps(network_data)) if network_data.auto_send: read_pipe.send(pickle.dumps(network_data)) else: # here is where the thread receives data from the outside # decode messages and reroute them accordingly cmds = node.recv() msg_type = cmds.pop(0).decode('utf-8') peer_uuid = uuid.UUID(bytes=cmds.pop(0)) sender_node_name = cmds.pop(0).decode('utf-8') if msg_type == "SHOUT": group = cmds.pop(0).decode('utf-8') read_pipe.send(cmds.pop(0)) elif msg_type == "WHISPER": read_pipe.send(cmds.pop(0)) # elif msg_type == "ENTER": # headers = json.loads(cmds.pop(0).decode('utf-8')) # print("NODE_MSG HEADERS: %s" % headers) # for key in headers: # print("key = {0}, value = {1}".format(key, headers[key])) # print("NODE_MSG CONT: %s" % cmds) node.stop()