示例#1
0
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()