Mercurial > zrpn
changeset 102:361178f45eb6
updated to pygnetic module
line wrap: on
line diff
--- a/zrpn/board.py Sun Jul 08 18:00:38 2012 +0200 +++ b/zrpn/board.py Thu Sep 06 21:35:41 2012 +0200 @@ -45,14 +45,11 @@ n[0] = xy[0] n[1] = xy[1] new_net_adds.append(n) - if not killed: self.printit() return animations, extra, new_net_adds - - def printit(self): print ("cells") for c in self.cells: @@ -65,7 +62,7 @@ for y, line in enumerate(self.cells): for x, c in enumerate(line): if c == cell: - return x,y + return x, y def apply_gravity(self, turn): animations = []
--- a/zrpn/client.py Sun Jul 08 18:00:38 2012 +0200 +++ b/zrpn/client.py Thu Sep 06 21:35:41 2012 +0200 @@ -7,10 +7,10 @@ import sgc import pygame -import pygame_network as net +import pygnetic as net import display -import packets +import messages from skelutil import data_dir @@ -45,26 +45,25 @@ self.connection.net_click(x, y) elif event.type == net.event.NETWORK: if event.net_type == net.event.NET_RECEIVED: - if event.p_type == packets.INIT: - self.display.load_board(event.packet.board, - event.packet.sprites) - elif event.p_type == packets.ANIMATIONS: - self.display.do_animations(event.packet.animations) - elif event.p_type == packets.SCORE: - if event.packet.score_num is not None: - self.lbl_score.config(text="score=\n" + str(event.packet.score_num)) - self.lbl_highscore.config(text="first=\n" + str(event.packet.highscore)) - elif event.p_type == packets.ADD: - self.display.add_zrpn(*event.packet) - self.client.step() + if event.msg_type == messages.INIT: + self.display.load_board(event.message.board, + event.message.sprites) + elif event.msg_type == messages.ANIMATIONS: + self.display.do_animations(event.message.animations) + elif event.msg_type == messages.SCORE: + if event.message.score_num is not None: + self.lbl_score.config(text="score=\n" + str(event.message.score_num)) + self.lbl_highscore.config(text="first=\n" + str(event.message.highscore)) + elif event.msg_type == messages.ADD: + self.display.add_zrpn(*event.message) + self.client.update() t = time.time() - self.start self.lbl_time.config(text="time\nwasted=\n" + str(round(t, 5))) self.display.frame() def main(): - net.event.init() - logging.basicConfig(level=logging.DEBUG) + net.init(True, logging_lvl=logging.DEBUG) Client('localhost', 10000).run() if __name__ == '__main__':
--- a/zrpn/main.py Sun Jul 08 18:00:38 2012 +0200 +++ b/zrpn/main.py Thu Sep 06 21:35:41 2012 +0200 @@ -53,9 +53,8 @@ def multi(self): self.remove() - import pygame_network as net - net.event.init() - logging.basicConfig(level=logging.DEBUG) + import pygnetic as net + net.init(True, logging_lvl=logging.DEBUG) ip = self["ip"].text ip = str(ip)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/zrpn/messages.py Thu Sep 06 21:35:41 2012 +0200 @@ -0,0 +1,7 @@ +import pygnetic as net +INIT = net.register('init', ('board', 'sprites')) +ANIMATIONS = net.register('animations', ('animations',)) +CLICK = net.register('click', ('x', 'y')) +SCORE = net.register('score', ('score_num', 'highscore')) +ADD = net.register('add', ('x', 'y', 'cell', 'board_ids')) +NEW_PLAYER = ('new_player', ('player_count',))
--- a/zrpn/packets.py Sun Jul 08 18:00:38 2012 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,7 +0,0 @@ -import pygame_network as net -INIT = net.register('init', ('board', 'sprites')) -ANIMATIONS = net.register('animations', ('animations',)) -CLICK = net.register('click', ('x', 'y')) -SCORE = net.register('score', ('score_num', 'highscore')) -ADD = net.register('add', ('x', 'y', 'cell', 'board_ids')) -NEW_PLAYER = ('new_player', ('player_count',))
--- a/zrpn/pygame_network/__init__.py Sun Jul 08 18:00:38 2012 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,12 +0,0 @@ -import packet -import connection -import syncobject -from client import Client -from server import Server -from receiver import Receiver -try: - import event -except ImportError: - pass - -register = packet.PacketManager.register
--- a/zrpn/pygame_network/client.py Sun Jul 08 18:00:38 2012 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,51 +0,0 @@ -import logging -import enet -from packet import PacketManager -from connection import Connection - -_logger = logging.getLogger(__name__) - - -class Client(object): - """Class representing network client - - Example: - client = pygame_network.client.Client() - connection = client.connect("localhost", 10000) - while True: - client.step() - """ - def __init__(self, connections_limit=1, channel_limit=0, in_bandwidth=0, out_bandwidth=0): - self.host = enet.Host(None, connections_limit, channel_limit, in_bandwidth, out_bandwidth) - self._peers = {} - self._peer_cnt = 0 - - def connect(self, address, port, channels=2, packet_manager=PacketManager): - peer_id = self._peer_cnt = self._peer_cnt + 1 - peer_id = str(peer_id) - # Can't register packets after connection - packet_manager._frozen = True - address = enet.Address(address, port) - peer = self.host.connect(address, channels, packet_manager.get_hash()) - peer.data = peer_id - connection = Connection(self, peer, packet_manager) - self._peers[peer_id] = connection - return connection - - def step(self, timeout=0): - if len(self._peers) == 0: - return - host = self.host - event = host.service(timeout) - while event is not None: - if event.type == enet.EVENT_TYPE_CONNECT: - _logger.info('Connected to %s', event.peer.address) - self._peers[event.peer.data]._connect() - elif event.type == enet.EVENT_TYPE_DISCONNECT: - _logger.info('Disconnected from %s', event.peer.address) - self._peers[event.peer.data]._disconnect() - del self._peers[event.peer.data] - elif event.type == enet.EVENT_TYPE_RECEIVE: - _logger.info('Received data from %s', event.peer.address) - self._peers[event.peer.data]._receive(event.packet.data, event.channelID) - event = host.check_events()
--- a/zrpn/pygame_network/connection.py Sun Jul 08 18:00:38 2012 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,157 +0,0 @@ -__all__ = ('Connection', - 'STATE_ACKNOWLEDGING_CONNECT', - 'STATE_ACKNOWLEDGING_DISCONNECT', - 'STATE_CONNECTED', - 'STATE_CONNECTING', - 'STATE_CONNECTION_PENDING', - 'STATE_CONNECTION_SUCCEEDED', - 'STATE_DISCONNECTED', - 'STATE_DISCONNECTING', - 'STATE_DISCONNECT_LATER', - 'STATE_ZOMBIE') - -import logging -from weakref import proxy -from functools import partial -import enet -from packet import PacketManager - -_logger = logging.getLogger(__name__) - -# dummy events -def _connected_event(connection): pass -def _disconnected_event(connection): pass -def _received_event(connection, channel, packet, packet_id): pass -def _response_event(connection, channel, packet, packet_id): pass - -STATE_ACKNOWLEDGING_CONNECT = enet.PEER_STATE_ACKNOWLEDGING_CONNECT -STATE_ACKNOWLEDGING_DISCONNECT = enet.PEER_STATE_ACKNOWLEDGING_DISCONNECT -STATE_CONNECTED = enet.PEER_STATE_CONNECTED -STATE_CONNECTING = enet.PEER_STATE_CONNECTING -STATE_CONNECTION_PENDING = enet.PEER_STATE_CONNECTION_PENDING -STATE_CONNECTION_SUCCEEDED = enet.PEER_STATE_CONNECTION_SUCCEEDED -STATE_DISCONNECTED = enet.PEER_STATE_DISCONNECTED -STATE_DISCONNECTING = enet.PEER_STATE_DISCONNECTING -STATE_DISCONNECT_LATER = enet.PEER_STATE_DISCONNECT_LATER -STATE_ZOMBIE = enet.PEER_STATE_ZOMBIE - - -class Connection(object): - """Class allowing to send packets - - It's created by by Client or Server, shouldn't be created manually. - - Sending is possible in two ways: - * using net_<packet_name> methods, where <packet_name> - is name of packet registered in PacketManager - * using send method with packet as argument - - Attributes: - parent - proxy to Client / Server instance - peer - Enet peer instance - """ - - def __init__(self, parent, peer, packet_manager=PacketManager): - self.parent = proxy(parent) - self.peer = peer - self._packet_cnt = 0 - self._packet_manager = packet_manager - self._receivers = [] - - def __del__(self): - self.peer.disconnect_now() - - def __getattr__(self, name): - parts = name.split('_', 1) - if len(parts) == 2 and parts[0] == 'net' and\ - parts[1] in self._packet_manager._packet_names: - p = partial(self._send, self._packet_manager.get_by_name(parts[1])) - p.__doc__ = "Send %s packet to remote host\n\n"\ - "Host.net_%s: return packet_id" % \ - (parts[1], self._packet_manager._packet_names[parts[1]].__doc__) - # add new method so __getattr__ is no longer needed - setattr(self, name, p) - return p - else: - raise AttributeError("'%s' object has no attribute '%s'" % - (type(self).__name__, name)) - - def send(self, packet, *args, **kwargs): - """Send packet to remote host - - Connection.send(packet, *args, **kwargs): return int - - packet - class created by PacketManager.register or packet name - - args and kwargs are used to initialize packet object. - Returns packet id which can be used to retrieve response from - Pygame event queue if sending was successful. - """ - if isinstance(packet, basestring): - packet = self._packet_manager.get_by_name(packet) - self._send(packet, *args, **kwargs) - - def _send(self, packet, *args, **kwargs): - _, channel, flags = self._packet_manager.get_params(packet) - flags = kwargs.get('flags', flags) - channel = kwargs.get('channel', channel) - packet_id = self._packet_cnt = self._packet_cnt + 1 - packet = packet(*args, **kwargs) - data = self._packet_manager.pack(packet_id, packet) - if self.peer.send(channel, enet.Packet(data, flags)) == 0: - return packet_id - - def _receive(self, data, channel): - packet_id, packet = self._packet_manager.unpack(data) - name = packet.__class__.__name__ - _logger.info('Received %s packet', name) - name = 'net_' + name - _received_event(self, channel, packet, packet_id) - for r in self._receivers: - getattr(r, name, r.on_recive)(channel, packet_id, packet) - - def _connect(self): - _connected_event(self) - for r in self._receivers: - r.on_connect() - - def _disconnect(self): - _disconnected_event(self) - for r in self._receivers: - r.on_disconnect() - - def disconnect(self): - """Request a disconnection. - """ - self.peer.disconnect() - - def disconnect_later(self): - """Request a disconnection from a peer, but only after all queued - outgoing packets are sent. - """ - self.peer.disconnect_later() - - def disconnect_now(self): - """Force an immediate disconnection. - """ - self.peer.disconnect_now() - - def add_receiver(self, receiver): - """Add new Receiver to handle packets. - - Connection.add_receiver(receiver) - - receiver - instance of Receiver subclass - """ - self._receivers.append(receiver) - receiver.connection = proxy(self) - - @property - def state(self): - """Connection state.""" - return self.peer.state - - @property - def address(self): - """Connection address.""" - return self.peer.address
--- a/zrpn/pygame_network/event.py Sun Jul 08 18:00:38 2012 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,66 +0,0 @@ -__all__ = ('NETWORK', - 'NET_CONNECTED', - 'NET_DISCONNECTED', - 'NET_RECEIVED', - 'NET_RESPONSE') - -from weakref import proxy -import pygame -from pygame.event import Event -from pygame.locals import USEREVENT -import connection - -NETWORK = USEREVENT + 1 -NET_CONNECTED = 0 -NET_DISCONNECTED = 1 -NET_RECEIVED = 2 -NET_RESPONSE = 3 # TODO: response !! - - -def init(event_val=1): - global NETWORK - NETWORK = USEREVENT + event_val - connection._connected_event = _connected_event - connection._disconnected_event = _disconnected_event - connection._received_event = _received_event - connection._response_event = _response_event - - -def _connected_event(connection): - pygame.event.post(Event(NETWORK, { - 'net_type': NET_CONNECTED, - #'connection': proxy(connection) - 'connection': connection - })) - - -def _disconnected_event(connection): - pygame.event.post(Event(NETWORK, { - 'net_type': NET_DISCONNECTED, - #'connection': proxy(connection) - 'connection': connection - })) - - -def _received_event(connection, channel, packet, packet_id): - pygame.event.post(Event(NETWORK, { - 'net_type': NET_RECEIVED, - #'connection': proxy(connection), - 'connection': connection, - 'channel': channel, - 'packet': packet, - 'p_id': packet_id, - 'p_type': packet.__class__ - })) - - -def _response_event(connection, channel, packet, packet_id): - pygame.event.post(Event(NETWORK, { - 'net_type': NET_RESPONSE, - #'connection': proxy(connection), - 'connection': connection, - 'channel': channel, - 'packet': packet, - 'p_id': packet_id, - 'p_type': packet.__class__ - }))
--- a/zrpn/pygame_network/packet.py Sun Jul 08 18:00:38 2012 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,167 +0,0 @@ -import logging -from collections import namedtuple -from weakref import WeakKeyDictionary, WeakValueDictionary -import enet -try: - raise ImportError # TODO: !! - import msgpack as s_lib - _packer = s_lib.Packer() - _unpacker = s_lib.Unpacker() - _pack = _packer.pack - _unpack = lambda data: _unpacker.feed(data) or _unpacker.unpack() -except ImportError: - import json as s_lib - _packer = s_lib.JSONEncoder() - _unpacker = s_lib.JSONDecoder() - _pack = _packer.encode - _unpack = _unpacker.decode - -_logger = logging.getLogger(__name__) - -__all__ = ('PacketManager', 'PacketError') - - -class PacketError(Exception): - pass - - -class PacketManager(object): - """Class allowing to register new packet types and pack/unpack them. - - example: - chat_msg = PacketManager.register('chat_msg', ('player', 'msg'), - enet.PACKET_FLAG_RELIABLE) - PacketManager.pack(chat_msg('Tom', 'Test message')) - """ - _packet_names = {} # mapping name -> packet - _packet_types = WeakValueDictionary() # mapping type_id -> packet - _packet_params = WeakKeyDictionary() # mapping packet -> type_id, send par - _type_id_cnt = 0 - _frozen = False - _hash = None - - def __init__(self): - # override class variables with instance variables - cls = self.__class__ - self._packet_names = cls._packet_names.copy() - self._packet_types = cls._packet_types.copy() - self._packet_params = cls._packet_params.copy() - self._type_id_cnt = cls._type_id_cnt - self._frozen = False - self._hash = None - - @classmethod - def register(cls, name, field_names=tuple(), channel=0, flags=enet.PACKET_FLAG_RELIABLE): - """Register new packet type - - PacketManager.register(name, field_names, channel, flags): return class - - name - name of packet class - field_names - names of packet fields - - Warning: All packets must be registered in THE SAME ORDER in client and - server, BEFORE creating any connection. - - Returns namedtuple class. - """ - if cls._frozen == True: - raise PacketError("Can't register new packets after "\ - "connection establishment") - type_id = cls._type_id_cnt = cls._type_id_cnt + 1 - packet = namedtuple(name, field_names) - cls._packet_names[name] = packet - cls._packet_types[type_id] = packet - cls._packet_params[packet] = (type_id, channel, flags) - return packet - - @classmethod - def pack(cls, packet_id, packet): - """Pack data to string - - PacketManager.pack(packet_id, packet_obj): return string - - packet_id - identifier of packet - packet - object of class created by register - - Returns packet packed in string, ready to send. - """ - type_id = cls._packet_params[packet.__class__][0] - packet = (type_id, packet_id) + packet - cls._packet_cnt = packet_id - return _pack(packet) - - @classmethod - def unpack(cls, data): - """Unpack data from string, return packet_id and packet - - PacketManager.unpack(data): return (packet_id, packet) - - data - packet data as a string - """ - packet = _unpack(data) - try: - type_id, packet_id = packet[:2] - return packet_id, cls._packet_types[type_id](*packet[2:]) - except KeyError: - _logger.warning('Unknown packet type_id: %s', type_id) - except: - _logger.warning('Packet unpacking error: %s', packet) - return None, None - - @classmethod - def get_by_name(cls, name): - """Returns packet class with given name - - PacketManager.get_by_name(name): return class - - name - name of packet - """ - return cls._packet_names[name] - - @classmethod - def get_by_type(cls, type_id): - """Returns packet class with given type_id - - PacketManager.get_by_type(name): return class - - type_id - type identifier of packet - """ - return cls._packet_types[type_id] - - @classmethod - def get_params(cls, packet): - """Return tuple containing type_id, channel and sending flags - - PacketManager.get_params(packet): return (int, int, int) - - packet - packet class created by register - """ - return cls._packet_params[packet] - - @classmethod - def get_hash(cls): - if cls._frozen: - if cls._hash is None: - ids = cls._packet_types.keys() - ids.sort() - l = list() - l.append(s_lib.__name__) - for i in ids: - p = cls._packet_types[i] - l.append((i, p.__name__, p._fields)) - # should be the same on 32 & 64 platforms - cls._hash = hash(tuple(l)) & 0xffffffff - return cls._hash - else: - _logger.warning('Attempt to get hash of not frozen PacketManager') - - -update_remoteobject = PacketManager.register('update_remoteobject', ( - 'type_id', - 'obj_id', - 'variables' -), 1, 0) -chat_msg = PacketManager.register('chat_msg', ( - 'player', - 'msg' -))
--- a/zrpn/pygame_network/receiver.py Sun Jul 08 18:00:38 2012 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,12 +0,0 @@ -class Receiver(object): - """Base class for objects receiving packets through net_packet_name - functions - """ - def on_connect(self): - pass - - def on_disconnect(self): - pass - - def on_recive(self, channel, packet_id, packet): - pass
--- a/zrpn/pygame_network/server.py Sun Jul 08 18:00:38 2012 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,62 +0,0 @@ -import logging -from weakref import proxy -from functools import partial -import enet -from receiver import Receiver -from packet import PacketManager -from connection import Connection - -_logger = logging.getLogger(__name__) - - -class Server(object): - packet_manager = PacketManager - receiver_cls = None - - def __init__(self, address='', port=0, connections_limit=4, # max 4095 - channel_limit=0, in_bandwidth=0, out_bandwidth=0): - address = enet.Address(address, port) - self.host = enet.Host(address, connections_limit, channel_limit, - in_bandwidth, out_bandwidth) - self.peers = {} - self._peer_cnt = 0 - self.packet_manager._frozen = True - - def step(self, timeout=0): - host = self.host - event = host.service(timeout) - while event is not None: - if event.type == enet.EVENT_TYPE_CONNECT: - if event.data == self.packet_manager.get_hash(): - _logger.info('Connection with %s accepted', event.peer.address) - peer_id = self._peer_cnt = self._peer_cnt + 1 - peer_id = str(peer_id) - event.peer.data = peer_id - connection = Connection(self, event.peer, self.packet_manager) - if issubclass(self.receiver_cls, Receiver): - receiver = self.receiver_cls() - receiver.server = proxy(self) - connection.add_receiver(receiver) - self.peers[peer_id] = connection - connection._connect() - else: - _logger.warning('Connection with %s refused, PacketManager'\ - ' hash incorrect', event.peer.address) - event.peer.disconnect_now() - elif event.type == enet.EVENT_TYPE_DISCONNECT: - _logger.info('Disconnected from %s', event.peer.address) - self.peers[event.peer.data]._disconnect() - del self.peers[event.peer.data] - elif event.type == enet.EVENT_TYPE_RECEIVE: - _logger.info('Received data from %s', event.peer.address) - self.peers[event.peer.data]._receive(event.packet.data, event.channelID) - event = host.check_events() - - def connections(self, exclude=[]): - if exclude is None: - return self.peers.itervalues() - else: - return (c for c in self.peers.itervalues() if c not in exclude) - - def receivers(self, exclude=[]): - return (c._receivers[0] for c in self.peers.itervalues() if c not in exclude)
--- a/zrpn/pygame_network/syncobject.py Sun Jul 08 18:00:38 2012 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,99 +0,0 @@ -from weakref import WeakKeyDictionary -from packet import * - -SYNCOBJECT_MODE_AUTO = 0 -SYNCOBJECT_MODE_MANUAL = 1 - - -class SyncObject(object): - """Base class for synchronized objects - - The base class for local objects to be automatically - synchronized with remote host. Derived class should replace sync_var - to specify a tuple of variable names for synchronization. - Each assignment to a variable defined in sync_var will notify - SyncObjectManager which, depending on sync_mode, will either - prepare update packet (SYNCOBJECT_MODE_AUTO) or - wait with preparation for send_changes call (SYNCOBJECT_MODE_MANUAL). - sync_flags overrides default enet sending flags. - """ - sync_var = () - sync_mode = SYNCOBJECT_MODE_AUTO - sync_flags = None - - def __init__(self, *args, **kwargs): - super(SyncObject, self).__init__(*args, **kwargs) - SyncObjectManager.register(self) - - def __setattr__(self, name, value): - object.__setattr__(self, name, value) - if name in self.sync_var: - SyncObjectManager.changed(self, name) - - def on_change(self, var_names): - """Callback when variable(s) was changed by remote host - """ - pass - - def on_reply(self, packet_id): - """Callback when there was reply to change from remote host - """ - pass - - def send_changes(self): - """Prepare update packet to send - - When sync_mode is SYNCOBJECT_MODE_MANUAL, - notify SyncObjectManager to prepare update packet - """ - pass - - def notify_change(self, var_name): - """Notify SyncObjectManager that variable was changed - """ - SyncObjectManager.changed(self, var_name) - - -class SyncObjectManager(object): - """Manager of SyncObject instances, shouldn't be used by user - - note: - Class used by SyncObject automatically, no need to use it. - """ - _known_types = {} - _sync_objs = WeakKeyDictionary() - _type_id_cnt = 0 - _obj_id_cnt = 0 - - @classmethod - def register(cls, obj): - """Registers new object deriving from SyncObject - """ - try: - type_id = cls._known_types[obj.__class__] - except KeyError: - type_id = cls._type_id_cnt + 1 - cls._known_types[obj.__class__] = type_id - cls._type_id_cnt = type_id - obj_id = cls._obj_id_cnt + 1 - cls._sync_objs[obj] = (type_id, obj_id, [False] * len(obj.sync_var)) - cls._obj_id_cnt = obj_id - - @classmethod - def changed(cls, obj, var_name): - """Prepares update packet - """ - print '%s@%s updated' % (var_name, obj) - # TODO: finish this - # TODO: push update packet to queue if in auto mode - -# TODO: pushing update packets from SyncObject -# TODO: mapping obj type and variables to ints - - -class RemoteObject(object): - """Class representing remote SyncObject locally - """ - pass - -# TODO: add methods faking SyncObject attributes
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/zrpn/pygnetic/__init__.py Thu Sep 06 21:35:41 2012 +0200 @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- +"""Network library for Pygame.""" + +import logging +import event +import message +import network +import serialization +from handler import Handler +from network import Server, Client + +__version__ = '1.0' +_logger = logging.getLogger(__name__) +register = message.message_factory.register + + +def init(events=False, event_val=None, logging_lvl=logging.INFO, + n_module=('enet', 'socket'), s_module=('msgpack', 'json')): + """Initialize network library. + + :param events: allow sending Pygame events (default False) + :param event_val: + set :const:`event.NETWORK` as + :const:`pygame.USEREVENT` + :attr:`event_val` (default: None) + :param logging_lvl: + level of logging messages (default :const:`logging.INFO` + (see: :ref:`logging-basic-tutorial`), None to skip initializing + logging module) + :param n_module: + name(s) of network library, first available will be used + (default: ['enet', 'socket']) + :type n_module: string or list of strings + :param s_module: + name(s) of serialization library, first available will be used + (default: ['msgpack', 'json']) + :type s_module: string or list of strings + :return: True if initialization ended with success + + .. note:: + Because of the dynamic loading of network library adapter, + :class:`Client` and :class:`Server` classes will only be + available after initialization. + """ + global _network_module, _serialization_module + if logging_lvl is not None: + logging.basicConfig(level=logging_lvl, + format='%(asctime)-8s %(levelname)-8s %(message)s', + datefmt='%H:%M:%S') + network.select_adapter(n_module) + if network.selected_adapter is not None: + _logger.info("Using %s", + network.selected_adapter.__name__.split('.')[-1]) + else: + _logger.critical("Can't find any network module") + return False + serialization.select_adapter(s_module) + if serialization.selected_adapter is not None: + _logger.info("Using %s", + serialization.selected_adapter.__name__.split('.')[-1]) + else: + _logger.critical("Can't find any serialization module") + return False + if events: + _logger.info("Enabling pygame events") + event.init(event_val) + return True
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/zrpn/pygnetic/_utils.py Thu Sep 06 21:35:41 2012 +0200 @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- + +import logging +from importlib import import_module + +_logger = logging.getLogger(__name__) + + +def get_adapter(a_type, name): + """Return library adapter + + a_type - adapter type + name - name of library + """ + a_name = name + '_adapter' + try: + return import_module('.'.join((a_type, a_name))) + except ImportError as e: + _logger.debug("%s: %s", e.__class__.__name__, e.message) + + +def find_adapter(a_type, names): + """Return first found adapter + + a_type - adapter type + names - string or list of strings with names of libraries + """ + if isinstance(names, basestring): + names = (names,) + for name in names: + a_name = name + '_adapter' + try: + return import_module('.'.join((a_type, a_name))) + except ImportError as e: + _logger.debug("%s: %s", e.__class__.__name__, e.message)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/zrpn/pygnetic/client.py Thu Sep 06 21:35:41 2012 +0200 @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- +"""Module containing base class for adapters representing network clients.""" + +import logging +import message + +_logger = logging.getLogger(__name__) + + +class Client(object): + """Class representing network client. + + :param int con_limit: maximum amount of created connections (default: 1) + :param args: additional arguments for :term:`network adapter` + :param kwargs: additional keyword arguments for :term:`network adapter` + """ + message_factory = message.message_factory + + def __init__(self, conn_limit=1, *args, **kwargs): + super(Client, self).__init__(*args, **kwargs) + self.conn_map = {} + _logger.info('Client created, connections limit: %d', conn_limit) + + def connect(self, host, port, message_factory=None, **kwargs): + """Connects to specified address. + + :param string host: IP address or name of host + :param int port: port of host + :param message_factory: + :class:`~message.MessageFactory` used for new connection + (default: :attr:`message_factory`) + :param kwargs: additional keyword arguments for :term:`network adapter` + :return: new :class:`~.connection.Connection` + """ + if message_factory is None: + message_factory = self.message_factory + _logger.info('Connecting to %s:%d', host, port) + message_factory.set_frozen() + connection, c_id = self._create_connection(host, port, + message_factory, **kwargs) + self.conn_map[c_id] = connection + return connection + + def update(self, timeout=0): + """Process network traffic and update connections. + + :param int timeout: + waiting time for network events in milliseconds + (default: 0 - no waiting) + """ + pass + + def _connect(self, c_id): + self.conn_map[c_id]._connect() + + def _disconnect(self, c_id): + self.conn_map[c_id]._disconnect() + del self.conn_map[c_id] + + def _receive(self, c_id, data, **kwargs): + self.conn_map[c_id]._receive(data, **kwargs)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/zrpn/pygnetic/connection.py Thu Sep 06 21:35:41 2012 +0200 @@ -0,0 +1,125 @@ +# -*- coding: utf-8 -*- +""" +Module containing base class for adapters representing network connections. +""" + +import re +import logging +from weakref import proxy +from functools import partial +import message +import event + +_logger = logging.getLogger(__name__) + + +class Connection(object): + """Class allowing to send messages + + :param parent: parent :class:`~.client.Client` or :class:`~.server.Server` + :param conn_obj: connection object + :param message_factory: :class:`~.message.MessageFactory` object + + .. note:: + It's created by :class:`~.client.Client` or :class:`~.server.Server` + and shouldn't be created manually. + + Sending is possible in two ways: + + * using :meth:`net_message_name` methods, where ``message_name`` + is name of message registered in :class:`~.message.MessageFactory` + * using :meth:`send` method with message as argument + """ + address = ('', '') + + def __init__(self, parent, conn_obj, message_factory, *args, **kwargs): + super(Connection, self).__init__(*args, **kwargs) + self.parent = proxy(parent) + self.message_factory = message_factory + self.message_factory.reset_context(self) + self.handlers = [] + self.data_sent = 0 + self.data_received = 0 + self.messages_sent = 0 + self.messages_received = 0 + + def __getattr__(self, name): + parts = name.split('_', 1) + if (len(parts) == 2 and parts[0] == 'net' and + parts[1] in self.message_factory._message_names): + p = partial(self._send_message, + self.message_factory.get_by_name(parts[1])) + p.__doc__ = "Send %s message to remote host\n\nHost.net_%s" % ( + parts[1], + self.message_factory._message_names[parts[1]].__doc__) + # add new method so __getattr__ is no longer needed + setattr(self, name, p) + return p + else: + raise AttributeError("'%s' object has no attribute '%s'" % + (type(self).__name__, name)) + + def send(self, message, *args, **kwargs): + """Send message to remote host. + + :param message: + class created by :meth:`~.message.MessageFactory.register` + or message name + :param args: parameters used to initialize message object + :param kwargs: keyword parameters used to initialize message object + """ + if isinstance(message, basestring): + message = self.message_factory.get_by_name(message) + self._send_message(message, *args, **kwargs) + + def _send_message(self, message, *args, **kwargs): + name = message.__name__ + params = self.message_factory.get_params(message)[1] + try: + message_ = message(*args, **kwargs) + except TypeError, e: + e, f = re.findall(r'[^_a-z](\d+)', e.message, re.I) + raise TypeError('%s takes exactly %d arguments (%d given)' % + (message.__doc__, int(e) - 1, int(f) - 1)) + data = self.message_factory.pack(message_) + _logger.info('Sent %s message to %s', name, self.address) + self.data_sent += len(data) + self.messages_sent += 1 + return self._send_data(data, **params) + + def _receive(self, data, **kwargs): + self.data_received += len(data) + for message in self.message_factory.unpack_all(data, self): + self.messages_received += 1 + name = message.__class__.__name__ + _logger.info('Received %s message from %s', name, self.address) + event.received(self, message) + for h in self.handlers: + getattr(h, 'net_' + name, h.on_recive)(message, **kwargs) + + def _connect(self): + _logger.info('Connected to %s', self.address) + event.connected(self) + for h in self.handlers: + h.on_connect() + + def _disconnect(self): + _logger.info('Disconnected from %s', self.address) + event.disconnected(self) + for h in self.handlers: + h.on_disconnect() + + def disconnect(self, *args): + """Request a disconnection. + + :param args: additional arguments for :term:`network adapter` + """ + pass + + def add_handler(self, handler): + """Add new Handler to handle messages. + + :param handler: instance of :class:`~.handler.Handler` subclass + """ + self.handlers.append(handler) + handler.connection = proxy(self)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/zrpn/pygnetic/discovery.py Thu Sep 06 21:35:41 2012 +0200 @@ -0,0 +1,147 @@ +import logging +import socket +import select +import threading +import SocketServer +from collections import OrderedDict +from itertools import islice +import message + +_logger = logging.getLogger(__name__) + +message_factory = message.MessageFactory() + +register = message_factory.register('register', ( + 'oid', 'name', 'host', 'port' +)) +get_servers = message_factory.register('get_servers', ( + 'oid', 'current', 'part_size' +)) +ping = message_factory.register('ping', ( + 'oid', 'sid' +)) +response = message_factory.register('response', ( + 'oid', 'value', 'error' +)) + + +class Errors(object): + NO_ERROR = 0 + TIMEOUT = 1 + + +class ServerHandler(SocketServer.BaseRequestHandler): + def handle(self): + data, socket = self.request + _logger.debug('Received data: %r', data) + message = message_factory.unpack(data) + if message is None: + return + name = message.__class__.__name__ + _logger.info('Received %s message from %s', name, self.client_address) + ret_val, err = getattr(self, 'net_' + name)(message) + ack_data = message_factory.pack(response(message.oid, ret_val, err)) + cnt = socket.sendto(ack_data, self.client_address) + _logger.info('Sent response to %s', self.client_address) + _logger.debug('Sent %d bytes: %r', cnt, ack_data) + + def unknown_msg(self, message): + name = message.__class__.__name__ + _logger.warning('Received unknown %s message from %s', + name, self.client_address) + + def net_register(self, message): + sid = self.server.id_cnt = self.server.id_cnt + 1 + self.server.servers[sid] = [message[1:], 0] + return sid, Errors.NO_ERROR + + def net_get_servers(self, message): + s = message.current * message.part_size + e = s + message.part_size + servers = self.server.servers + d = {k: servers[k][0] for k in islice(servers.iterkeys(), s, e)} + return d, Errors.NO_ERROR + + def net_ping(self, message): + s = self.server.servers.get(message.sid) + if s is not None: + s[1] = 0 + return None, Errors.NO_ERROR + else: + return None, Errors.TIMEOUT + + +class DiscoveryServer(SocketServer.UDPServer, object): + allow_reuse_address = True + max_packet_size = 8192 + cleaner_period = 10 + ping_timeout = 6 + + def __init__(self, host='', port=5000): + super(DiscoveryServer, self).__init__((host, port), ServerHandler) + self.servers = OrderedDict() + self.id_cnt = 0 + self.c_stop = threading.Event() + self.c_thread = threading.Thread(target=self.cleaner) + self.c_thread.daemon = True + self.c_thread.start() + + def __del__(self): + self.c_stop.set() + + def cleaner(self): + while not self.c_stop.is_set(): + keys = [] + for k, v in self.servers.iteritems(): + v[1] += 1 + if v[1] > self.ping_timeout: + keys.append(k) + for k in keys: + del self.servers[k] + self.c_stop.wait(self.cleaner_period) + + +class DiscoveryClient(object): + max_packet_size = 8192 + + def __init__(self, host, port=5000): + self.address = socket.gethostbyname(host), port + self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self.response = None + self.callback = None + self.oid = 0 + + def _send_msg(self, message, *args): + oid = self.oid = self.oid + 1 + data = message_factory.pack(message(oid, *args)) + cnt = self.socket.sendto(data, self.address) + self.response = None + name = message.__class__.__name__ + _logger.info('Sent %s message to %s', name, self.address) + _logger.debug('Sent %d bytes: %r', cnt, data) + return oid + + def update(self, timeout=0): + r = select.select([self.socket], [], [], timeout / 1000.0)[0] + if len(r) > 0: + data, address = self.socket.recvfrom(self.max_packet_size) + if address == self.address: + _logger.debug('Received data: %r', data) + r = self.response = message_factory.unpack(data) + _logger.info('Received response from %s', address) + if callable(self.callback): + self.callback(r) + else: + _logger.info('Unexpected data from %s', address) + + def close(self): + self.socket.close() + + def register(self, name, host, port): + return self._send_msg(register, name, host, port) + + def ping(self, sid): + return self._send_msg(ping, sid) + + def get_servers(self, current=0, part_size=10): + return self._send_msg(get_servers, current, part_size)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/zrpn/pygnetic/event.py Thu Sep 06 21:35:41 2012 +0200 @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- +"""Module defining Pygame events.""" + +__all__ = ('NETWORK', + 'NET_DISCONNECTED', + 'NET_CONNECTED', + 'NET_ACCEPTED', + 'NET_RECEIVED') + +NETWORK = 30 +NET_DISCONNECTED = 0 +NET_CONNECTED = 1 +NET_ACCEPTED = 2 +NET_RECEIVED = 3 + + +def accepted(connection): + pass + + +def connected(connection): + pass + + +def disconnected(connection): + pass + + +def received(connection, message): + pass + + +def init(event_val=None): + global NETWORK, connected, disconnected, received + import pygame + from pygame.event import Event + from pygame.locals import USEREVENT + if event_val is not None: + NETWORK = USEREVENT + event_val + + def _accepted(connection): + pygame.event.post(Event(NETWORK, { + 'net_type': NET_ACCEPTED, + #'connection': proxy(connection) + 'connection': connection + })) + accepted = _accepted + + def _connected(connection): + pygame.event.post(Event(NETWORK, { + 'net_type': NET_CONNECTED, + #'connection': proxy(connection) + 'connection': connection + })) + connected = _connected + + def _disconnected(connection): + pygame.event.post(Event(NETWORK, { + 'net_type': NET_DISCONNECTED, + #'connection': proxy(connection) + 'connection': connection + })) + disconnected = _disconnected + + def _received(connection, message, channel=0): + pygame.event.post(Event(NETWORK, { + 'net_type': NET_RECEIVED, + #'connection': proxy(connection), + 'connection': connection, + 'message': message, + 'msg_type': message.__class__ + })) + received = _received
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/zrpn/pygnetic/handler.py Thu Sep 06 21:35:41 2012 +0200 @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +"""Module containing Handler base class.""" + +class Handler(object): + """Base class for objects handling messages through + :meth:`net_message_name` methods + """ + def on_connect(self): + """Called when connection is established.""" + pass + + def on_disconnect(self): + """Called when connection is closed.""" + pass + + def on_recive(self, message, **kwargs): + """Called when message is received, but no corresponding + net_message_name method exist. + + :param message: received message + :param kwargs: + additional keyword arguments from :term:`network adapter` + """ + pass
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/zrpn/pygnetic/message.py Thu Sep 06 21:35:41 2012 +0200 @@ -0,0 +1,167 @@ +# -*- coding: utf-8 -*- +"""Module containing MessageFactory and predefined messages""" + +import logging +from collections import namedtuple +from weakref import WeakKeyDictionary, WeakValueDictionary +import serialization + +_logger = logging.getLogger(__name__) + + +class MessageFactory(object): + """Class allowing to register new message types and pack/unpack them. + + :param s_adapter: + :term:`serialization adapter` + (default: None - library selected with :func:`.init`) + """ + def __init__(self, s_adapter=None): + self._message_names = {} # name -> message + self._message_types = WeakValueDictionary() # type_id -> message + self._message_params = WeakKeyDictionary() # message -> type_id, send kwargs + if s_adapter is None: + self.s_adapter = serialization + else: + self.s_adapter = s_adapter + self._type_id_cnt = 0 + self._frozen = False + self._hash = None + + def register(self, name, field_names=tuple(), **kwargs): + """Register new message type. + + :param name: name of message class + :param field_names: list of names of message fields + :param kwargs: additional keyword arguments for send method + :return: message class (namedtuple) + """ + if self._frozen == True: + _logger.warning("Can't register new messages after connection " + "establishment") + type_id = self._type_id_cnt = self._type_id_cnt + 1 + packet = namedtuple(name, field_names) + self._message_names[name] = packet + self._message_types[type_id] = packet + self._message_params[packet] = (type_id, kwargs) + return packet + + def pack(self, message): + """Pack data to string. + + :param message: object of class created by register + :return: string + """ + type_id = self._message_params[message.__class__][0] + message = (type_id,) + message + data = self.s_adapter.pack(message) + _logger.debug("Packing message (length: %d)", len(data)) + return data + + def set_frozen(self): + """Disable ability to register new messages to allow generation + of hash. + """ + self._frozen = True + + def reset_context(self, context): + """Prepares object to behave as context for stream unpacking. + + :param context: object which will be prepared + """ + context._unpacker = self.s_adapter.unpacker() + + def _process_message(self, message): + try: + type_id = message[0] + return self._message_types[type_id](*message[1:]) + except KeyError: + _logger.error('Unknown message type_id: %s', type_id) + except: + _logger.error('Message unpacking error: %s', message) + + def unpack(self, data): + """Unpack message from string. + + :param data: packed message data as a string + :return: message + """ + _logger.debug("Unpacking message (length: %d)", len(data)) + message = self.s_adapter.unpack(data) + if message is not None: + return self._process_message(message) + else: + _logger.error('Data corrupted') + _logger.debug('Data: %r', data) + + def unpack_all(self, data, context): + """Feed unpacker with data from stream and unpack all messages. + + :param data: packed message(s) data as a string + :param context: object previously prepared with :meth:`reset_context` + :return: iterator over messages + """ + _logger.debug("Unpacking data (length: %d)", len(data)) + context._unpacker.feed(data) + try: + for message in context._unpacker: + yield self._process_message(message) + except: + _logger.error('Data corrupted') + self._reset_unpacker() # prevent from corrupting next data + return + + def get_by_name(self, name): + """Returns message class with given name. + + :param name: name of message + :return: message class (namedtuple) + """ + return self._message_names[name] + + def get_by_type(self, type_id): + """Returns message class with given type_id. + + :param type_id: type identifier of message + :return: message class (namedtuple) + """ + return self._message_types[type_id] + + def get_params(self, message_cls): + """Return tuple containing type_id, and sending keyword arguments + + :param message_cls: message class created by register + :return: int, dict + """ + return self._message_params[message_cls] + + def get_hash(self): + """Calculate and return hash. + + Hash depends on registered messages and used serializing library. + + :return: int + """ + if self._frozen: + if self._hash is None: + ids = self._message_types.keys() + ids.sort() + l = list() + a = getattr(self.s_adapter, 'selected_adapter', self.s_adapter) + l.append(a.__name__) + for i in ids: + p = self._message_types[i] + l.append((i, p.__name__, p._fields)) + # should be the same on 32 & 64 platforms + self._hash = hash(tuple(l)) & 0xffffffff + return self._hash + else: + _logger.warning('Attempt to get hash of not frozen MessageFactory') + + +message_factory = MessageFactory() +#update_remoteobject = message_factory.register('update_remoteobject', ( +# 'type_id', +# 'obj_id', +# 'variables' +#), channel=1, flags=0)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/zrpn/pygnetic/network/__init__.py Thu Sep 06 21:35:41 2012 +0200 @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +"""Network adapters.""" + +from .. import _utils + +selected_adapter = None + + +def select_adapter(names): + global selected_adapter + selected_adapter = _utils.find_adapter(__name__, names) + + +def get_adapter(name): + return _utils.find_adapter(__name__, name) + + +class Client(object): + "Class creating its instance based on selected :term:`network adapter`." + + def __new__(cls, *args, **kwargs): + "Create instance of Client class depending on selected network adapter" + b = cls.__bases__ + if selected_adapter is None: + raise AttributeError("Client adapter is not selected") + if Client in b: # creation by inheritance + i = b.index(Client) + 1 + cls.__bases__ = b[:i] + (selected_adapter.Client,) + b[i:] + return super(cls, cls).__new__(cls, *args, **kwargs) + else: # direct object creation + # can't assign to __bases__ - bugs.python.org/issue672115 + return selected_adapter.Client(*args, **kwargs) + + +class Server(object): + "Class creating its instance based on selected :term:`network adapter`." + + def __new__(cls, *args, **kwargs): + "Create instance of Server class depending on selected network adapter" + b = cls.__bases__ + if selected_adapter is None: + raise AttributeError("Client adapter is not selected") + if Server in b: # creation by inheritance + i = b.index(Server) + 1 + cls.__bases__ = b[:i] + (selected_adapter.Server,) + b[i:] + return super(Server, cls).__new__(cls, *args, **kwargs) + else: # direct object creation + # can't assign to __bases__ - bugs.python.org/issue672115 + return selected_adapter.Server(*args, **kwargs)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/zrpn/pygnetic/network/enet_adapter.py Thu Sep 06 21:35:41 2012 +0200 @@ -0,0 +1,113 @@ +# -*- coding: utf-8 -*- +"""Module containing network adapter for enet.""" + +import logging +import enet +from .. import connection, server, client + +_logger = logging.getLogger(__name__) + + +class Connection(connection.Connection): + def __init__(self, parent, peer, message_factory, *args, **kwargs): + super(Connection, self).__init__(parent, peer, message_factory, + *args, **kwargs) + self.peer = peer + + def __del__(self): + self.peer.disconnect_now() + + def _send_data(self, data, channel=0, flags=enet.PACKET_FLAG_RELIABLE, + **kwargs): + self.peer.send(channel, enet.Packet(data, flags)) + + def disconnect(self, when=0): + """Request a disconnection. + + when - type of disconnection + 0 - disconnect with acknowledge + 1 - disconnect after sending all messages + -1 - disconnect without acknowledge + """ + if when == 0: + self.peer.disconnect() + elif when == 1: + self.peer.disconnect_later() + else: + self.peer.disconnect_now() + + @property + def connected(self): + """Connection state.""" + return self.peer.state == enet.PEER_STATE_CONNECTED + + @property + def address(self): + """Connection address.""" + address = self.peer.address + return address.host, address.port + + +class Server(server.Server): + def __init__(self, host='', port=0, conn_limit=4, *args, **kwargs): + super(Server, self).__init__(host, port, conn_limit, *args, **kwargs) + host = enet.Address(host, port) + self.host = enet.Host(host, conn_limit, *args, **kwargs) + self._peer_cnt = 0 + + def update(self, timeout=0): + host = self.host + event = host.service(timeout) + while event is not None: + if event.type == enet.EVENT_TYPE_CONNECT: + peer_id = str(self._peer_cnt + 1) + if self._accept(Connection, event.peer, event.peer.address, + peer_id, event.data): + event.peer.data = peer_id + self._peer_cnt += 1 + else: + event.peer.disconnect_now() + elif event.type == enet.EVENT_TYPE_DISCONNECT: + self._disconnect(event.peer.data) + elif event.type == enet.EVENT_TYPE_RECEIVE: + self._receive(event.peer.data, event.packet.data, + channel=event.channelID) + event = host.check_events() + + @property + def address(self): + """Server address.""" + address = self.host.address + return address.host, address.port + + +class Client(client.Client): + def __init__(self, conn_limit=1, *args, **kwargs): + super(Client, self).__init__(*args, **kwargs) + self.host = enet.Host(None, conn_limit) + self._peer_cnt = 0 + + def _create_connection(self, host, port, message_factory, channels=1, + **kwargs): + host = enet.Address(host, port) + peer_id = self._peer_cnt = self._peer_cnt + 1 + peer_id = str(peer_id) + peer = self.host.connect(host, channels, message_factory.get_hash()) + peer.data = peer_id + connection = Connection(self, peer, message_factory) + return connection, peer_id + + def update(self, timeout=0): + if len(self.conn_map) == 0: + return + host = self.host + event = host.service(timeout) + while event is not None: + if event.type == enet.EVENT_TYPE_CONNECT: + self._connect(event.peer.data) + elif event.type == enet.EVENT_TYPE_DISCONNECT: + self._disconnect(event.peer.data) + elif event.type == enet.EVENT_TYPE_RECEIVE: + self._receive(event.peer.data, event.packet.data, + channel=event.channelID) + event = host.check_events()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/zrpn/pygnetic/network/socket_adapter.py Thu Sep 06 21:35:41 2012 +0200 @@ -0,0 +1,142 @@ +# -*- coding: utf-8 -*- +"""Module containing network adapter for socket (asyncore.dispacher).""" + +import logging +import socket +import struct +import asyncore +from collections import deque +from .. import connection, server, client + +_logger = logging.getLogger(__name__) +_connect_struct = struct.Struct('!I') + + +class Dispacher(asyncore.dispatcher, object): + pass + + +class Connection(connection.Connection, Dispacher): + # maximum amount of data received / sent at once + recv_buffer_size = 4096 + + def __init__(self, parent, socket, message_factory, *args, **kwargs): + super(Connection, self).__init__( + parent, socket, message_factory, + socket, None, + * args, **kwargs) + #self.send_queue = deque() + self.send_buffer = bytearray() + #self.recv_buffer = bytearray(b'\0' * self.recv_buffer_size) + + def _send_data(self, data, **kwargs): + self.send_buffer.extend(data) + self._send_part() + +# def _send_data2(self, data, **kwargs): +# if len(self.send_buffer) == 0: +# self.send_buffer = data +# else: +# self.send_queue.append(data) +# self._send_part() + + def handle_write(self): + self._send_part() + + def _send_part(self): + try: + num_sent = asyncore.dispatcher.send(self, self.send_buffer) + except socket.error: + self.handle_error() + return + self.send_buffer = self.send_buffer[num_sent:] + + def writable(self): + return (not self.connected) or len(self.send_buffer) + + def handle_read(self): + data = self.recv(self.recv_buffer_size) + if data: + self._receive(data) + +# def handle_read2(self): +# # tinkering with dispatcher internal variables, +# # because it doesn't support socket.recv_into +# try: +# num_rcvd = self.socket.recv_into(self.recv_buffer) +# if not num_rcvd: +# self.handle_close() +# else: +# self._receive(self.recv_buffer[:num_rcvd]) +# except socket.error, why: +# if why.args[0] in asyncore._DISCONNECTED: +# self.handle_close() +# else: +# self.handle_error() +# return + + def handle_connect(self): + self.addr = self.socket.getpeername() + self._connect() + + def handle_close(self): + self.parent._disconnect(self.socket.fileno()) + self.close() + + def log_info(self, message, type='info'): + return getattr(_logger, type)(message) + + def disconnect(self, *args): + self.parent._disconnect(self.socket.fileno()) + self.close() + + @property + def address(self): + return self.addr + + +class Server(server.Server, Dispacher): + connection = Connection + + def __init__(self, host='', port=0, con_limit=4, *args, **kwargs): + super(Server, self).__init__( + host, port, con_limit, + None, None, + *args, **kwargs) + self.create_socket(socket.AF_INET, socket.SOCK_STREAM) + # disable Nagle buffering algorithm + self.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + self.set_reuse_addr() + self.bind((host, port)) + self.listen(con_limit) + + def handle_accept(self): + pair = self.accept() + if pair is not None: + sock, addr = pair + sock.settimeout(0.5) # TODO: sth better? + mf_hash = _connect_struct.unpack(sock.recv(4))[0] + sock.setblocking(0) + if not self._accept(Connection, sock, addr, self._fileno, mf_hash): + sock.close() + + def update(self, timeout=0): + asyncore.loop(timeout / 1000.0, False, None, 1) + + +class Client(client.Client): + def __init__(self, conn_limit=0, *args, **kwargs): + super(Client, self).__init__(*args, **kwargs) + self._sock_cnt = 0 + + def _create_connection(self, host, port, message_factory, **kwargs): + conn = Connection(self, None, message_factory) + conn.create_socket(socket.AF_INET, socket.SOCK_STREAM) + # disable Nagle buffering algorithm + conn.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + conn.connect((host, port)) + conn.socket.send(_connect_struct.pack(message_factory.get_hash())) + return conn, conn.socket.fileno() + + def update(self, timeout=0): + asyncore.loop(timeout / 1000.0, False, self.conn_map, 1)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/zrpn/pygnetic/serialization/__init__.py Thu Sep 06 21:35:41 2012 +0200 @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +"""Serialization adapters.""" + +from .. import _utils + +selected_adapter = None +pack = unpack = unpacker = None + + +def select_adapter(names): + global selected_adapter, pack, unpack, unpacker + selected_adapter = _utils.find_adapter(__name__, names) + if selected_adapter is not None: + pack = selected_adapter.pack + unpack = selected_adapter.unpack + unpacker = selected_adapter.unpacker + + +def get_adapter(name): + return _utils.find_adapter(__name__, name)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/zrpn/pygnetic/serialization/json_adapter.py Thu Sep 06 21:35:41 2012 +0200 @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +"""Module containing serialization adapter for json.""" + +import json + + +class JSONDecoder(json.JSONDecoder): + """JSONDecoder with streaming feature""" + def __init__(self): + super(JSONDecoder, self).__init__() + self.buffer = bytearray() + + def feed(self, data): + self.buffer.extend(data) + + def decode(self): + try: + obj, end = self.scan_once(self.buffer.decode(), 0) + del self.buffer[:end] + return obj + except: + raise StopIteration('No more data to decode.') + + next = decode + + +def unpack(*args): + try: + return json.loads(*args) + except ValueError: + pass + + +pack = json.dumps +unpacker = JSONDecoder
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/zrpn/pygnetic/serialization/msgpack_adapter.py Thu Sep 06 21:35:41 2012 +0200 @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +"""Module containing serialization adapter for msgpack.""" + +import msgpack + +pack = msgpack.packb +unpack = msgpack.unpackb +unpacker = msgpack.Unpacker
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/zrpn/pygnetic/server.py Thu Sep 06 21:35:41 2012 +0200 @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- +"""Module containing base class for adapters representing network servers.""" + +import logging +from weakref import proxy +import message +import event +from handler import Handler + +_logger = logging.getLogger(__name__) + + +class Server(object): + """Class representing network server. + + :param string host: IP address or name of host (default: "" - any) + :param int port: port of host (default: 0 - any) + :param int con_limit: maximum amount of created connections (default: 4) + :param args: additional arguments for :term:`network adapter` + :param kwargs: additional keyword arguments for :term:`network adapter` + """ + message_factory = message.message_factory + handler = None + + def __init__(self, host='', port=0, conn_limit=4, *args, **kwargs): + super(Server, self).__init__(*args, **kwargs) + _logger.info('Server created %s:%d, connections limit: %d', + host, port, conn_limit) + self.message_factory.set_frozen() + self.conn_map = {} + self.conn_limit = conn_limit + + def update(self, timeout=0): + """Process network traffic and update connections. + + :param int timeout: + waiting time for network events in milliseconds + (default: 0 - no waiting) + """ + pass + + def _accept(self, connection, socket, address, c_id, mf_hash): + if mf_hash == self.message_factory.get_hash(): + _logger.info('Connection with %s accepted', address) + conn = connection(self, socket, self.message_factory) + if self.handler is not None and issubclass(self.handler, Handler): + handler = self.handler() + handler.server = proxy(self) + conn.add_handler(handler) + self.conn_map[c_id] = conn + event.accepted(self) + conn._connect() + return True + else: + _logger.info('Connection with %s refused, MessageFactory'\ + ' hash incorrect', address) + return False + + def _disconnect(self, c_id): + conn = self.conn_map.get(c_id) + if conn is not None: + conn._disconnect() + del self.conn_map[c_id] + + def _receive(self, c_id, data, **kwargs): + self.conn_map[c_id]._receive(data, **kwargs) + + def connections(self, exclude=None): + """Returns iterator over connections. + + :param exclude: list of connections to exclude + :return: + iterator over + :class:`connections <.connection.Connection>` + """ + if exclude is None: + return self.conn_map.itervalues() + else: + return (c for c in self.conn_map.itervalues() if c not in exclude) + + def handlers(self, exclude=None): + """Returns iterator over handlers. + + :param exclude: list of connections to exclude + :return: + iterator over + :class:`handlers <.handler.Handler>` + """ + if exclude is None: + return (c.handlers[0] for c in self.conn_map.itervalues()) + else: + return (c.handlers[0] for c in self.conn_map.itervalues() + if c not in exclude) + + @property + def address(self): + """Server address.""" + return None, None
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/zrpn/pygnetic/syncobject.py Thu Sep 06 21:35:41 2012 +0200 @@ -0,0 +1,104 @@ +# -*- coding: utf-8 -*- +"""Module containing base classes for synchronized objects.""" + +from weakref import WeakKeyDictionary +from message import * + + +class Mode(object): + AUTO = 0 + MANUAL = 1 + + +class SyncObject(object): + """Base class for synchronized objects + + The base class for local objects to be automatically + synchronized with remote host. Derived class should replace sync_var + to specify a tuple of variable names for synchronization. + Each assignment to a variable defined in sync_var will notify + SyncObjectManager which, depending on sync_mode, will either + prepare update message (Mode.AUTO) or + wait with preparation for send_changes call (Mode.MANUAL). + sync_flags overrides default enet sending flags. + """ + sync_var = () + sync_mode = Mode.AUTO + sync_flags = None + + def __init__(self, *args, **kwargs): + super(SyncObject, self).__init__(*args, **kwargs) + SyncObjectManager.register(self) + + def __setattr__(self, name, value): + object.__setattr__(self, name, value) + if name in self.sync_var: + SyncObjectManager.changed(self, name) + + def on_change(self, var_names): + """Callback when variable(s) was changed by remote host + """ + pass + + def on_reply(self, message_id): + """Callback when there was reply to change from remote host + """ + pass + + def send_changes(self): + """Prepare update message to send + + When sync_mode is SYNCOBJECT_MODE_MANUAL, + notify SyncObjectManager to prepare update message + """ + pass + + def notify_change(self, var_name): + """Notify SyncObjectManager that variable was changed + """ + SyncObjectManager.changed(self, var_name) + + +class SyncObjectManager(object): + """Manager of SyncObject instances, shouldn't be used by user + + note: + Class used by SyncObject automatically, no need to use it. + """ + _known_types = {} + _sync_objs = WeakKeyDictionary() + _type_id_cnt = 0 + _obj_id_cnt = 0 + + @classmethod + def register(cls, obj): + """Registers new object deriving from SyncObject + """ + try: + type_id = cls._known_types[obj.__class__] + except KeyError: + type_id = cls._type_id_cnt + 1 + cls._known_types[obj.__class__] = type_id + cls._type_id_cnt = type_id + obj_id = cls._obj_id_cnt + 1 + cls._sync_objs[obj] = (type_id, obj_id, set()) + cls._obj_id_cnt = obj_id + + @classmethod + def changed(cls, obj, var_name): + """Prepares update message + """ + cls._sync_objs[obj][2].add(var_name) + # TODO: finish this + # TODO: push update message to queue if in auto mode + +# TODO: pushing update messages from SyncObject +# TODO: mapping obj type and variables to ints + + +class RemoteObject(object): + """Class representing remote SyncObject locally + """ + pass + +# TODO: add methods faking SyncObject attributes
--- a/zrpn/server.py Sun Jul 08 18:00:38 2012 +0200 +++ b/zrpn/server.py Thu Sep 06 21:35:41 2012 +0200 @@ -1,23 +1,23 @@ import time import logging -import pygame_network as net +import pygnetic as net import board -import packets +import messages -class ClientReceiver(net.Receiver): +class ClientReceiver(net.Handler): board = board.Board() def __init__(self): self.click_time = time.time() self.score = 0 - def net_click(self, channel, packet_id, packet): + def net_click(self, message, **kwargs): t = time.time() if t - self.click_time < 0.5: return self.click_time = t - animations = self.board.kill_one(packet) + animations = self.board.kill_one(message) ans, extra, net_adds = self.board.update() animations.extend(ans) #TODO: scoring. check for animations of kind == 'collumn_row' @@ -32,7 +32,7 @@ print self.score, id(self) - max_score = max(r.score for r in self.server.receivers()) + max_score = max(r.score for r in self.server.handlers()) self.connection.net_score(self.score, max_score) for connection in self.server.connections(): connection.net_animations(animations) @@ -43,22 +43,22 @@ connection.net_score(None, max_score) def on_connect(self): - #logging.debug("ClientReceiver.on_connect") + logging.info('Connected :)') self.connection.net_init(self.board.board, self.board.cells) -class Server(object): +class Server(): def __init__(self, port): - self.server = net.Server(port=port, connections_limit=10) - self.server.receiver_cls = ClientReceiver + self.server = net.Server(port=port, conn_limit=10) + self.server.handler = ClientReceiver def run(self): while True: - self.server.step(1000) + self.server.update(1000) def main(): - logging.basicConfig(level=logging.DEBUG) + net.init(logging_lvl=logging.DEBUG) Server(10000).run() if __name__ == '__main__':
