From 7a63889ff41abe5075b7ca8a9e35b0fde19560da Mon Sep 17 00:00:00 2001 From: Christoph Egger Date: Tue, 15 Nov 2011 15:57:37 +0100 Subject: Import python-enet_0.0~svn24.orig.tar.xz [dgit import orig python-enet_0.0~svn24.orig.tar.xz] --- ChangeLog | 26 ++ LICENSE | 11 + README | 58 ++++ enet.pyx | 1007 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ setup.py | 47 +++ test_client.py | 34 ++ test_enet.py | 168 ++++++++++ test_server.py | 29 ++ 8 files changed, 1380 insertions(+) create mode 100644 ChangeLog create mode 100644 LICENSE create mode 100644 README create mode 100644 enet.pyx create mode 100644 setup.py create mode 100644 test_client.py create mode 100644 test_enet.py create mode 100644 test_server.py diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..629c3ea --- /dev/null +++ b/ChangeLog @@ -0,0 +1,26 @@ +==== pyenet 1.3 (In Development) ==== + * Rewrote to build with Cython instead of Pyrex + * Fixed bug where a double-free would happen because the Event object would + create a new Packet object every time the packet attribute was accessed. + * Add some unit testing + * Add test_server and test_client + * Builds against enet 1.3 + +==== pyenet 1.2 (In Development) ==== + + * Fix build against current enet CVS trunk + + +==== pyenet from enet cvs ==== +Sat Nov 1 00:36:02 PST 2003 Scott Robinson + Began developing test interface after a day of coding... + +Mon Nov 3 08:46:33 PST 2003 Scott Robinson + Added documentation to all classes, functions, and attributes. + While adding documentation, added accessors to a few more attributes. + Cleaned up a few methods to match proper pyrex behavior. + Removed a few, and added a couple of obvious todos for the future. + Fixed Address.__getattr__ extra \0s in the case .host. + +Fri Feb 13 18:18:04 PST 2004 Scott Robinson + Added Socket class for use with select and poll. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d765c19 --- /dev/null +++ b/LICENSE @@ -0,0 +1,11 @@ +Copyright (C) 2003, Scott Robinson +Copyright (c) 2009,2010 Andrew Resch +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the name of the nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README b/README new file mode 100644 index 0000000..b832bb6 --- /dev/null +++ b/README @@ -0,0 +1,58 @@ +pyenet 1.3 + +About +===================== + +pyenet is a python wrapper for the ENet library by Lee Salzman, + http://enet.bespin.org + +It was originally written by Scott Robinson and is +currently maintained by Andrew Resch at + http://code.google.com/p/pyenet + +pyenet is licensed under the BSD license, see LICENSE for details. +enet is licensed under the MIT license, see http://enet.bespin.org/License.html + +Dependencies +===================== + +Building pyenet requires all the same dependencies as enet plus Cython and, +obviously, Python. + +Installation +===================== + +The first step is to download the enet sources from http://enet.bespin.org and +extract it to the enet/ directory in pyenet's source directory. You could also +check out the enet source code from their CVS repository. + +This version of pyenet requires enet 1.3. + +Next step is to run the setup.py build: + + $ python setup.py build + +Once that is complete, install the new pyenet module: + + # python setup.py install + +Usage +===================== + +Once you have installed pyenet, you only need to import the enet module to +start using enet in your project. + +Example server: + +>>> import enet +>>> host = enet.Host(enet.Address("localhost", 33333), 1, 0, 0) +>>> event = host.service(0) + +Example client: + +>>> import enet +>>> host = enet.Host(None, 1, 0, 0) +>>> peer = host.connect(enet.Address("localhost", 33333), 1) + +More information on usage can be obtained from: + http://enet.bespin.org/Tutorial.html diff --git a/enet.pyx b/enet.pyx new file mode 100644 index 0000000..0dda8ff --- /dev/null +++ b/enet.pyx @@ -0,0 +1,1007 @@ +import atexit + +from cpython cimport bool + +cdef extern from "enet/types.h": + ctypedef unsigned char enet_uint8 + ctypedef unsigned short enet_uint16 + ctypedef unsigned int enet_uint32 + ctypedef unsigned int size_t + +cdef extern from "enet/enet.h": + + ctypedef enet_uint32 ENetVersion + # forward declaration + ctypedef struct ENetPeer + ctypedef struct ENetHost + + cdef enum: + ENET_HOST_ANY = 0 + ENET_HOST_BROADCAST = 0xFFFFFFFF + ENET_PORT_ANY = 0 + + ctypedef int ENetSocket + + ctypedef struct ENetAddress: + enet_uint32 host + enet_uint16 port + + ctypedef enum ENetPacketFlag: + ENET_PACKET_FLAG_RELIABLE = (1 << 0) + ENET_PACKET_FLAG_UNSEQUENCED = (1 << 1) + ENET_PACKET_FLAG_NO_ALLOCATE = (1 << 2) + ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT = (1 << 3) + + ctypedef struct ENetPacket: + size_t referenceCount + enet_uint32 flags + enet_uint8 *data + size_t dataLength + + ctypedef enum ENetPeerState: + ENET_PEER_STATE_DISCONNECTED = 0 + ENET_PEER_STATE_CONNECTING = 1 + ENET_PEER_STATE_ACKNOWLEDGING_CONNECT = 2 + ENET_PEER_STATE_CONNECTION_PENDING = 3 + ENET_PEER_STATE_CONNECTION_SUCCEEDED = 4 + ENET_PEER_STATE_CONNECTED = 5 + ENET_PEER_STATE_DISCONNECT_LATER = 6 + ENET_PEER_STATE_DISCONNECTING = 7 + ENET_PEER_STATE_ACKNOWLEDGING_DISCONNECT = 8 + ENET_PEER_STATE_ZOMBIE = 9 + + ctypedef struct ENetPeer: + ENetHost *host + enet_uint16 outgoingPeerID + enet_uint16 incomingPeerID + enet_uint32 connectID + enet_uint8 outgoingSessionID + enet_uint8 incomingSessionID + ENetAddress address + char *data + ENetPeerState state + size_t channelCount + enet_uint32 incomingBandwidth + enet_uint32 outgoingBandwidth + enet_uint32 incomingBandwidthThrottleEpoch + enet_uint32 outgoingBandwidthThrottleEpoch + enet_uint32 incomingDataTotal + enet_uint32 outgoingDataTotal + enet_uint32 lastSendTime + enet_uint32 lastReceiveTime + enet_uint32 nextTimeout + enet_uint32 earliestTimeout + enet_uint32 packetLossEpoch + enet_uint32 packetsSent + enet_uint32 packetsLost + enet_uint32 packetLoss + enet_uint32 packetLossVariance + enet_uint32 packetThrottle + enet_uint32 packetThrottleLimit + enet_uint32 packetThrottleCounter + enet_uint32 packetThrottleEpoch + enet_uint32 packetThrottleAcceleration + enet_uint32 packetThrottleDeceleration + enet_uint32 packetThrottleInterval + enet_uint32 lastRoundTripTime + enet_uint32 lowestRoundTripTime + enet_uint32 lastRoundTripTimeVariance + enet_uint32 highestRoundTripTimeVariance + enet_uint32 roundTripTime + enet_uint32 roundTripTimeVariance + enet_uint32 mtu + enet_uint32 windowSize + enet_uint32 reliableDataInTransit + enet_uint16 outgoingReliableSequenceNumber + int needsDispatch + enet_uint16 incomingUnsequencedGroup + enet_uint16 outgoingUnsequencedGroup + enet_uint32 unsequencedWindow + enet_uint32 eventData + + ctypedef struct ENetHost: + ENetSocket socket + ENetAddress address + enet_uint32 incomingBandwidth + enet_uint32 outgoingBandwidth + ENetPeer *peers + size_t peerCount + size_t channelLimit + enet_uint32 totalSentData + enet_uint32 totalSentPackets + enet_uint32 totalReceivedData + enet_uint32 totalReceivedPackets + + ctypedef enum ENetEventType: + ENET_EVENT_TYPE_NONE = 0 + ENET_EVENT_TYPE_CONNECT = 1 + ENET_EVENT_TYPE_DISCONNECT = 2 + ENET_EVENT_TYPE_RECEIVE = 3 + + ctypedef struct ENetEvent: + ENetEventType type + ENetPeer *peer + enet_uint8 channelID + enet_uint32 data + ENetPacket *packet + + # Global functions + int enet_initialize() + void enet_deinitialize() + + # Address functions + int enet_address_set_host(ENetAddress *address, char *hostName) + int enet_address_get_host_ip(ENetAddress *address, char *hostName, size_t nameLength) + int enet_address_get_host(ENetAddress *address, char *hostName, size_t nameLength) + + # Packet functions + ENetPacket* enet_packet_create(char *dataContents, size_t dataLength, enet_uint32 flags) + void enet_packet_destroy(ENetPacket *packet) + int enet_packet_resize(ENetPacket *packet, size_t dataLength) + + # Host functions + int enet_host_compress_with_range_coder(ENetHost *host) + ENetHost* enet_host_create(ENetAddress *address, size_t peerCount, size_t channelLimit, enet_uint32 incomingBandwidth, enet_uint32 outgoingBandwidth) + void enet_host_destroy(ENetHost *host) + ENetPeer* enet_host_connect(ENetHost *host, ENetAddress *address, size_t channelCount, enet_uint32 data) + void enet_host_broadcast(ENetHost *host, enet_uint8 channelID, ENetPacket *packet) + void enet_host_channel_limit(ENetHost *host, size_t channelLimit) + void enet_host_bandwidth_limit(ENetHost *host, enet_uint32 incomingBandwidth, enet_uint32 outgoingBandwidth) + void enet_host_flush(ENetHost *host) + int enet_host_check_events(ENetHost *host, ENetEvent *event) + int enet_host_service(ENetHost *host, ENetEvent *event, enet_uint32 timeout) + + # Peer functions + void enet_peer_throttle_configure(ENetPeer *peer, enet_uint32 interval, enet_uint32 acceleration, enet_uint32 deacceleration) + int enet_peer_send(ENetPeer *peer, enet_uint8 channelID, ENetPacket *packet) + ENetPacket* enet_peer_receive(ENetPeer *peer, enet_uint8 *channelID) + void enet_peer_ping(ENetPeer *peer) + void enet_peer_reset(ENetPeer *peer) + void enet_peer_disconnect(ENetPeer *peer, enet_uint32 data) + void enet_peer_disconnect_now(ENetPeer *peer, enet_uint32 data) + void enet_peer_disconnect_later(ENetPeer *peer, enet_uint32 data) + +cdef enum: + MAXHOSTNAME = 257 + +PACKET_FLAG_RELIABLE = ENET_PACKET_FLAG_RELIABLE +PACKET_FLAG_UNSEQUENCED = ENET_PACKET_FLAG_UNSEQUENCED +PACKET_FLAG_NO_ALLOCATE = ENET_PACKET_FLAG_NO_ALLOCATE +PACKET_FLAG_UNRELIABLE_FRAGMENT = ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT + +EVENT_TYPE_NONE = ENET_EVENT_TYPE_NONE +EVENT_TYPE_CONNECT = ENET_EVENT_TYPE_CONNECT +EVENT_TYPE_DISCONNECT = ENET_EVENT_TYPE_DISCONNECT +EVENT_TYPE_RECEIVE = ENET_EVENT_TYPE_RECEIVE + +PEER_STATE_DISCONNECTED = ENET_PEER_STATE_DISCONNECTED +PEER_STATE_CONNECTING = ENET_PEER_STATE_CONNECTING +PEER_STATE_ACKNOWLEDGING_CONNECT = ENET_PEER_STATE_ACKNOWLEDGING_CONNECT +PEER_STATE_CONNECTION_PENDING = ENET_PEER_STATE_CONNECTION_PENDING +PEER_STATE_CONNECTION_SUCCEEDED = ENET_PEER_STATE_CONNECTION_SUCCEEDED +PEER_STATE_CONNECTED = ENET_PEER_STATE_CONNECTED +PEER_STATE_DISCONNECT_LATER = ENET_PEER_STATE_DISCONNECT_LATER +PEER_STATE_DISCONNECTING = ENET_PEER_STATE_DISCONNECTING +PEER_STATE_ACKNOWLEDGING_DISCONNECT = ENET_PEER_STATE_ACKNOWLEDGING_DISCONNECT +PEER_STATE_ZOMBIE = ENET_PEER_STATE_ZOMBIE + +cdef class Socket: + """ + Socket (int socket) + + DESCRIPTION + + An ENet socket. + + Can be used with select and poll. + """ + + cdef ENetSocket _enet_socket + + def fileno(self): + return self._enet_socket + +cdef class Address: + """ + Address (str address, int port) + + ATTRIBUTES + + str host Hostname referred to by the Address. + int port Port referred to by the Address. + + DESCRIPTION + + An ENet address and port pair. + + When instantiated, performs a resolution upon 'address'. However, if + 'address' is None, enet.HOST_ANY is assumed. + """ + + cdef ENetAddress _enet_address + + def __init__(self, host, port): + if host is not None: + # Convert the hostname to a byte string if needed + self.host = host if isinstance(host, bytes) else bytes(host, "ascii") + else: + self.host = None + self.port = port + + def __str__(self): + return "{0}:{1}".format(self.host, self.port) + + def __richcmp__(self, obj, op): + if isinstance(obj, Address): + if op == 2: + # This is a '==' operation + return (obj.host == self.host) and (obj.port == self.port) + elif op == 3: + # This is a '!=' operation + return (obj.host != self.host) or (obj.port != self.port) + + raise NotImplementedError + + property host: + def __get__(self): + cdef char host[MAXHOSTNAME] + + if self._enet_address.host == ENET_HOST_ANY: + return "*" + elif self._enet_address.host: + if enet_address_get_host_ip(&self._enet_address, host, MAXHOSTNAME): + raise IOError("Resolution failure!") + return unicode(host, "ascii") + + def __set__(self, value): + if not value or value == "*": + self._enet_address.host = ENET_HOST_ANY + else: + if enet_address_set_host(&self._enet_address, value): + raise IOError("Resolution failure!") + + property hostname: + def __get__(self): + cdef char host[MAXHOSTNAME] + + if self._enet_address.host == ENET_HOST_ANY: + return "*" + elif self._enet_address.host: + if enet_address_get_host(&self._enet_address, host, MAXHOSTNAME): + raise IOError("Resolution failure!") + return unicode(host, "ascii") + + property port: + def __get__(self): + return self._enet_address.port + + def __set__(self, value): + self._enet_address.port = value + +cdef class Packet: + """ + Packet (str dataContents, int flags) + + ATTRIBUTES + + str data Contains the data for the packet. + int flags Flags modifying delivery of the Packet: + + enet.PACKET_FLAG_RELIABLE Packet must be received by the target peer + and resend attempts should be made until + the packet is delivered. + + enet.PACKET_FLAG_UNSEQUENCED Packet will not be sequenced with other + packets not supported for reliable + packets. + + enet.PACKET_FLAG_NO_ALLOCATE Packet will not allocate data and user + must supply it instead. + + enet.PACKET_FLAG_UNRELIABLE_FRAGMENT Packet will be fragmented using + unreliable (instead of reliable) + sends if it exceeds the MTU... + + DESCRIPTION + + An ENet data packet that may be sent to or received from a peer. + + """ + + cdef ENetPacket *_enet_packet + cdef bool sent + + def __init__(self, data=None, flags=0): + if data is not None: + self._enet_packet = enet_packet_create(data, len(data), flags) + + # This will get set to True when a peer.send() is called with the Packet + # to ensure we don't try to destroy this packet as ENET will handle that + # for us. + self.sent = False + + def __dealloc__(self): + if self.is_valid() and not self.sent: + enet_packet_destroy(self._enet_packet) + + def is_valid(self): + if self._enet_packet: + return True + else: + return False + + property data: + def __get__(self): + if self.is_valid(): + return (self._enet_packet.data)[:self._enet_packet.dataLength] + else: + raise MemoryError("Packet has not been initiliazed properly!") + + property dataLength: + def __get__(self): + if self.is_valid(): + return self._enet_packet.dataLength + else: + raise MemoryError("Packet has not been initiliazed properly!") + + property flags: + def __get__(self): + if self.is_valid(): + return self._enet_packet.flags + else: + raise MemoryError("Packet has not been initiliazed properly!") + + property sent: + def __get__(self): + return self.sent + def __set__(self, value): + self.sent = value + +cdef class Peer: + """ + Peer () + + ATTRIBUTES + + Address address + int state The peer's current state which is one of + enet.PEER_STATE_* + int packetLoss Mean packet loss of reliable packets as a ratio with + respect to the constant enet.PEER_PACKET_LOSS_SCALE. + int packetThrottleAcceleration + int packetThrottleDeceleration + int packetThrottleInterval + int roundTripTime Mean round trip time (RTT), in milliseconds, + between sending a reliable packet and receiving + its acknowledgement. + int incomingPeerID + + DESCRIPTION + + An ENet peer which data packets may be sent or received from. + + This class should never be instantiated directly, but rather via + enet.Host.connect or enet.Event.Peer. If you try to access any members + of a Peer without being properly instantiated from a Host or Event + object then a MemoryError will be raised. + + """ + + cdef ENetPeer *_enet_peer + + def __richcmp__(self, obj, op): + if isinstance(obj, Peer): + if op == 2: + return self.address == obj.address + elif op == 3: + return self.address != obj.address + raise NotImplementedError + + def send(self, channelID, Packet packet): + """ + send (int channelID, Packet packet) + + Queues a packet to be sent. + + returns 0 on sucess, < 0 on failure + """ + + if self.check_valid() and packet.is_valid(): + packet.sent = True + return enet_peer_send(self._enet_peer, channelID, packet._enet_packet) + + def receive(self, unsigned char channelID): + """ + receive (int channelID) + + Attempts to dequeue any incoming queued packet. + """ + + if self.check_valid(): + packet = Packet() + ( packet)._enet_packet = enet_peer_receive(self._enet_peer, &channelID) + + if packet._enet_packet: + return packet + else: + return None + + def reset(self): + """ + reset () + + Forcefully disconnects a peer. + """ + + if self.check_valid(): + enet_peer_reset(self._enet_peer) + + def ping(self): + """ + ping () + + Sends a ping request to a peer. + """ + + if self.check_valid(): + enet_peer_ping(self._enet_peer) + + def disconnect(self, data=0): + """ + disconnect () + + Request a disconnection from a peer. + """ + + if self.check_valid(): + enet_peer_disconnect(self._enet_peer, data) + + def disconnect_later(self, data=0): + """ + disconnect_later () + + Request a disconnection from a peer, but only after all queued outgoing + packets are sent. + """ + + if self.check_valid(): + enet_peer_disconnect_later(self._enet_peer, data) + + def disconnect_now(self, data=0): + """ + disconnect_now () + + Force an immediate disconnection from a peer. + """ + + if self.check_valid(): + enet_peer_disconnect_now(self._enet_peer, data) + + + def check_valid(self): + """ + check_valid () + + Returns True if there is a valid enet_peer set + Raises a Memory error if not + + """ + if self._enet_peer: + return True + else: + raise MemoryError("Empty Peer object accessed!") + + property host: + def __get__(self): + if self.check_valid(): + # be a bit like pickle here. otherwise Host + # would create a new enet host inside __init__ + h = Host.__new__(Host) + ( h)._enet_host = self._enet_peer.host + return h + + property outgoingPeerID: + def __get__(self): + if self.check_valid(): + return self._enet_peer.outgoingPeerID + + property incomingPeerID: + def __get__(self): + if self.check_valid(): + return self._enet_peer.incomingPeerID + + property connectID: + def __get__(self): + if self.check_valid(): + return self._enet_peer.connectID + + property outgoingSessionID: + def __get__(self): + if self.check_valid(): + return self._enet_peer.outgoingSessionID + + property incomingSessionID: + def __get__(self): + if self.check_valid(): + return self._enet_peer.incomingSessionID + + property address: + def __get__(self): + if self.check_valid(): + a = Address(None, 0) + (
a)._enet_address = self._enet_peer.address + return a + + property data: + def __get__(self): + if self.check_valid(): + return self._enet_peer.data + + def __set__(self, value): + if self.check_valid(): + self._enet_peer.data = value + + property state: + def __get__(self): + if self.check_valid(): + return self._enet_peer.state + + property channelCount: + def __get__(self): + if self.check_valid(): + return self._enet_peer.channelCount + + property incomingBandwidth: + def __get__(self): + if self.check_valid(): + return self._enet_peer.incomingBandwidth + + property outgoingBandwidth: + def __get__(self): + if self.check_valid(): + return self._enet_peer.outgoingBandwidth + + property incomingBandwidthThrottleEpoch: + def __get__(self): + if self.check_valid(): + return self._enet_peer.incomingBandwidthThrottleEpoch + + property outgoingBandwidthThrottleEpoch: + def __get__(self): + if self.check_valid(): + return self._enet_peer.outgoingBandwidthThrottleEpoch + + property incomingDataTotal: + def __get__(self): + if self.check_valid(): + return self._enet_peer.incomingDataTotal + + property outgoingDataTotal: + def __get__(self): + if self.check_valid(): + return self._enet_peer.outgoingDataTotal + + property lastSendTime: + def __get__(self): + if self.check_valid(): + return self._enet_peer.lastSendTime + + property lastReceiveTime: + def __get__(self): + if self.check_valid(): + return self._enet_peer.lastReceiveTime + + property nextTimeout: + def __get__(self): + if self.check_valid(): + return self._enet_peer.nextTimeout + + property earliestTimeout: + def __get__(self): + if self.check_valid(): + return self._enet_peer.earliestTimeout + + property packetLossEpoch: + def __get__(self): + if self.check_valid(): + return self._enet_peer.packetLossEpoch + + property packetsSent: + def __get__(self): + if self.check_valid(): + return self._enet_peer.packetsSent + + property packetsLost: + def __get__(self): + if self.check_valid(): + return self._enet_peer.packetsLost + + property packetLoss: + def __get__(self): + if self.check_valid(): + return self._enet_peer.packetLoss + + property packetLossVariance: + def __get__(self): + if self.check_valid(): + return self._enet_peer.packetLossVariance + + property packetThrottle: + def __get__(self): + if self.check_valid(): + return self._enet_peer.packetThrottle + + property packetThrottleLimit: + def __get__(self): + if self.check_valid(): + return self._enet_peer.packetThrottleLimit + + property packetThrottleCounter: + def __get__(self): + if self.check_valid(): + return self._enet_peer.packetThrottleCounter + + property packetThrottleEpoch: + def __get__(self): + if self.check_valid(): + return self._enet_peer.packetThrottleEpoch + + property packetThrottleAcceleration: + def __get__(self): + if self.check_valid(): + return self._enet_peer.packetThrottleAcceleration + + def __set__(self, value): + if self.check_valid(): + enet_peer_throttle_configure(self._enet_peer, + self.packetThrottleInterval, value, + self.packetThrottleDeceleration) + + property packetThrottleDeceleration: + def __get__(self): + if self.check_valid(): + return self._enet_peer.packetThrottleDeceleration + + def __set__(self, value): + if self.check_valid(): + enet_peer_throttle_configure(self._enet_peer, + self.packetThrottleInterval, + self.packetThrottleAcceleration, value) + + property packetThrottleInterval: + def __get__(self): + if self.check_valid(): + return self._enet_peer.packetThrottleInterval + + def __set__(self, value): + if self.check_valid(): + enet_peer_throttle_configure( + self._enet_peer, value, self.packetThrottleAcceleration, + self.packetThrottleDeceleration) + + property lastRoundTripTime: + def __get__(self): + if self.check_valid(): + return self._enet_peer.lastRoundTripTime + + property lowestRoundTripTime: + def __get__(self): + if self.check_valid(): + return self._enet_peer.lowestRoundTripTime + + property lastRoundTripTimeVariance: + def __get__(self): + if self.check_valid(): + return self._enet_peer.lastRoundTripTimeVariance + + property highestRoundTripTimeVariance: + def __get__(self): + if self.check_valid(): + return self._enet_peer.highestRoundTripTimeVariance + + property roundTripTime: + def __get__(self): + if self.check_valid(): + return self._enet_peer.roundTripTime + + property roundTripTimeVariance: + def __get__(self): + if self.check_valid(): + return self._enet_peer.roundTripTimeVariance + + property mtu: + def __get__(self): + if self.check_valid(): + return self._enet_peer.mtu + + property windowSize: + def __get__(self): + if self.check_valid(): + return self._enet_peer.windowSize + + property reliableDataInTransit: + def __get__(self): + if self.check_valid(): + return self._enet_peer.reliableDataInTransit + + property outgoingReliableSequenceNumber: + def __get__(self): + if self.check_valid(): + return self._enet_peer.outgoingReliableSequenceNumber + + property needsDispatch: + def __get__(self): + if self.check_valid(): + return self._enet_peer.needsDispatch + + property incomingUnsequencedGroup: + def __get__(self): + if self.check_valid(): + return self._enet_peer.incomingUnsequencedGroup + + property outgoingUnsequencedGroup: + def __get__(self): + if self.check_valid(): + return self._enet_peer.outgoingUnsequencedGroup + + property eventData: + def __get__(self): + if self.check_valid(): + return self._enet_peer.eventData + +cdef class Event: + """ + Event () + + ATTRIBUTES + + int type Type of the event. Will be enet.EVENT_TYPE_*. + Peer peer Peer that generated the event. + int channelID + Packet packet + + DESCRIPTION + + An ENet event as returned by enet.Host.service. + + This class should never be instantiated directly. + """ + + cdef ENetEvent _enet_event + cdef Packet _packet + + def __init__(self): + self._packet = None + + property type: + def __get__(self): + return self._enet_event.type + + property peer: + def __get__(self): + peer = Peer() + ( peer)._enet_peer = self._enet_event.peer + return peer + + property channelID: + def __get__(self): + return self._enet_event.channelID + + property data: + def __get__(self): + return self._enet_event.data + + property packet: + def __get__(self): + if not self._packet: + self._packet = Packet() + ( self._packet)._enet_packet = self._enet_event.packet + return self._packet + +cdef class Host: + """ + Host (Address address, int peerCount, int channelLimit, + int incomingBandwidth, int outgoingBandwidth) + + ATTRIBUTES + + Address address Internet address of the host. + Socket socket The socket the host services. + int incomingBandwidth Downstream bandwidth of the host. + int outgoingBandwidth Upstream bandwidth of the host. + + DESCRIPTION + + An ENet host for communicating with peers. + + If 'address' is None, then the Host will be client only. + """ + + cdef ENetHost *_enet_host + cdef bool dealloc + + def __init__ (self, Address address=None, peerCount=0, channelLimit=0, + incomingBandwidth=0, outgoingBandwidth=0): + + if address: + self._enet_host = enet_host_create(&address._enet_address, peerCount, channelLimit, incomingBandwidth, outgoingBandwidth) + else: + self._enet_host = enet_host_create(NULL, peerCount, channelLimit, incomingBandwidth, outgoingBandwidth) + + if not self._enet_host: + raise MemoryError("Unable to create host structure!") + self.dealloc = True + + def __cinit__(self): + self.dealloc = False + self._enet_host = NULL + + def __dealloc__(self): + if self.dealloc: + enet_host_destroy(self._enet_host) + + def connect(self, Address address, channelCount, data=0): + """ + Peer connect (Address address, int channelCount, int data) + + Initiates a connection to a foreign host and returns a Peer. + """ + + if self._enet_host: + peer = Peer() + ( peer)._enet_peer = enet_host_connect( + self._enet_host, &address._enet_address, channelCount, data) + + if not ( peer)._enet_peer: + raise IOError("Connection failure!") + + return peer + + def check_events(self): + """ + Checks for any queued events on the host and dispatches one if available + """ + + if self._enet_host: + event = Event() + result = enet_host_check_events( + self._enet_host, &( event)._enet_event) + + if result < 0: + raise IOError("Servicing error - probably disconnected.") + elif result == 0: + return None + else: + return event + + def service(self, timeout): + """ + Event service (int timeout) + + Waits for events on the host specified and shuttles packets between + the host and its peers. The timeout is in milliseconds. + """ + + if self._enet_host: + event = Event() + result = enet_host_service( + self._enet_host, &( event)._enet_event, timeout) + + if result < 0: + raise IOError("Servicing error - probably disconnected.") + else: + return event + + def flush(self): + """ + flush () + + Sends any queued packets on the host specified to its designated peers. + """ + + if self._enet_host: + enet_host_flush(self._enet_host) + + def broadcast(self, channelID, Packet packet): + """ + broadcast (int channelID, Packet packet) + + Queues a packet to be sent to all peers associated with the host. + """ + + if self._enet_host: + if packet.is_valid(): + packet.sent = True + enet_host_broadcast(self._enet_host, channelID, packet._enet_packet) + + def compress_with_range_coder(self): + """ + Sets the packet compressor the host should use to the default range coder + """ + + if self._enet_host: + return enet_host_compress_with_range_coder(self._enet_host); + + property socket: + def __get__(self): + socket = Socket() + ( socket)._enet_socket = self._enet_host.socket + return socket + + property address: + def __get__(self): + if self._enet_host: + a = Address(None, 0) + (
a)._enet_address = self._enet_host.address + return a + + property incomingBandwidth: + def __get__(self): + return self._enet_host.incomingBandwidth + + def __set__(self, value): + enet_host_bandwidth_limit(self._enet_host, + value, self.outgoingBandwidth) + + property outgoingBandwidth: + def __get__(self): + return self._enet_host.outgoingBandwidth + + def __set__(self, value): + enet_host_bandwidth_limit(self._enet_host, + self.incomingBandwidth, value) + + property peers: + def __get__(self): + cdef size_t i + peers = [] + for i from 0 <= i < self.peerCount: + peer = Peer() + ( peer)._enet_peer = &self._enet_host.peers[i] + peers.append(peer) + return peers + + property peerCount: + def __get__(self): + return self._enet_host.peerCount + + property channelLimit: + def __get__(self): + return self._enet_host.channelLimit + + def __set__(self, value): + enet_host_channel_limit(self._enet_host, value) + + property totalSentData: + def __get__(self): + return self._enet_host.totalSentData + + def __set__(self, value): + self._enet_host.totalSentData = value + + property totalSentPackets: + def __get__(self): + return self._enet_host.totalSentPackets + + def __set__(self, value): + self._enet_host.totalSentPackets = value + + property totalReceivedData: + def __get__(self): + return self._enet_host.totalReceivedData + + def __set__(self, value): + self._enet_host.totalReceivedData = value + + property totalReceivedPackets: + def __get__(self): + return self._enet_host.totalReceivedPackets + + def __set__(self, value): + self._enet_host.totalReceivedPackets = value + +def _enet_atexit(): + enet_deinitialize() + +enet_initialize() +atexit.register(_enet_atexit) diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..95d55e8 --- /dev/null +++ b/setup.py @@ -0,0 +1,47 @@ +from distutils.core import setup +from distutils.extension import Extension +from Cython.Distutils import build_ext + +import glob +import sys + +source_files = ["enet.pyx"] + +_enet_files = glob.glob("enet/*.c") + +if not _enet_files: + print("You need to download and extract the enet 1.3 source to enet/") + print("Download the source from: http://enet.bespin.org/SourceDistro.html") + print("See the README for more instructions") + sys.exit(1) + +source_files.extend(_enet_files) + +define_macros = [('HAS_POLL', None), + ('HAS_FCNTL', None), + ('HAS_MSGHDR_FLAGS', None), + ('HAS_SOCKLEN_T', None) ] + +libraries = [] + +if sys.platform == 'win32': + define_macros.extend([('WIN32', None)]) + libraries.extend(['ws2_32', 'Winmm']) + +if sys.platform != 'darwin': + define_macros.extend([('HAS_GETHOSTBYNAME_R', None), ('HAS_GETHOSTBYADDR_R', None)]) + +ext_modules = [ + Extension( + "enet", + extra_compile_args=["-O3"], + sources=source_files, + include_dirs=["enet/include/"], + define_macros=define_macros, + libraries=libraries)] + +setup( + name = 'enet', + cmdclass = {'build_ext': build_ext}, + ext_modules = ext_modules +) diff --git a/test_client.py b/test_client.py new file mode 100644 index 0000000..7b29101 --- /dev/null +++ b/test_client.py @@ -0,0 +1,34 @@ +import enet +import os + +SHUTDOWN_MSG = b"SHUTDOWN" +MSG_NUMBER = 10 + +host = enet.Host(None, 1, 0, 0, 0) +peer = host.connect(enet.Address(b"localhost", 54301), 1) + +counter = 0 +run = True +while run: + event = host.service(1000) + if event.type == enet.EVENT_TYPE_CONNECT: + print("%s: CONNECT" % event.peer.address) + elif event.type == enet.EVENT_TYPE_DISCONNECT: + print("%s: DISCONNECT" % event.peer.address) + run = False + continue + elif event.type == enet.EVENT_TYPE_RECEIVE: + print("%s: IN: %r" % (event.peer.address, event.packet.data)) + continue + msg = os.urandom(40) + packet = enet.Packet(msg) + peer.send(0, packet) + + counter += 1 + if counter >= MSG_NUMBER: + msg = SHUTDOWN_MSG + peer.send(0, enet.Packet(msg)) + host.service(0) + peer.disconnect() + + print("%s: OUT: %r" % (peer.address, msg)) diff --git a/test_enet.py b/test_enet.py new file mode 100644 index 0000000..b7a6820 --- /dev/null +++ b/test_enet.py @@ -0,0 +1,168 @@ +import unittest +import enet + +class TestAddress(unittest.TestCase): + def test_host(self): + self.assertEquals(enet.Address("127.0.0.1", 9999).host, "127.0.0.1") + self.assertEquals(enet.Address("localhost", 9999).host, "127.0.0.1") + self.assertEquals(enet.Address(None, 9999).host, "*") + self.assertRaises(IOError, enet.Address, "foo.bar.baz.999", 9999) + + def test_port(self): + self.assertEquals(enet.Address("127.0.0.1", 9999).port, 9999) + self.assertRaises(TypeError, enet.Address, "127.0.0.1", "foo") + + def test_hostname(self): + import socket + self.assertEquals(enet.Address(socket.gethostname(), 9999).hostname, socket.gethostname()) + self.assertEquals(enet.Address(None, 9999).hostname, "*") + + def test_str(self): + self.assertEquals(enet.Address("127.0.0.1", 9999).__str__(), "127.0.0.1:9999") + + def test_richcmp(self): + self.assertTrue(enet.Address("127.0.0.1", 9999) == enet.Address("127.0.0.1", 9999)) + self.assertTrue(enet.Address("127.0.0.1", 9999) != enet.Address("127.0.0.1", 8888)) + self.assertFalse(enet.Address("127.1.1.1", 1992) == enet.Address("127.0.0.1", 9999)) + +class TestPacket(unittest.TestCase): + def test_data(self): + self.assertEquals(enet.Packet(b"foo\0bar").data, b"foo\0bar") + self.assertRaises(MemoryError, getattr, enet.Packet(), "data") + + def test_dataLength(self): + self.assertEquals(enet.Packet(b"foobar").dataLength, 6) + self.assertEquals(enet.Packet(b"foo\0bar").dataLength, 7) + self.assertRaises(MemoryError, getattr, enet.Packet(), "dataLength") + + def test_flags(self): + self.assertEquals(enet.Packet(b"foobar").flags, 0) + self.assertEquals(enet.Packet(b"foobar", enet.PACKET_FLAG_UNSEQUENCED).flags, enet.PACKET_FLAG_UNSEQUENCED) + self.assertRaises(MemoryError, getattr, enet.Packet(), "flags") + +class TestHost(unittest.TestCase): + def setUp(self): + self.client = enet.Host(None, 1, 0, 0, 0) + self.server = enet.Host(enet.Address("localhost", 54301), 1, 0, 0, 0) + self.peer = self.client.connect(enet.Address("localhost", 54301), 1) + self.assertEquals(self.peer.state, enet.PEER_STATE_CONNECTING) + + def tearDown(self): + del self.client + del self.server + del self.peer + + def test_connect(self): + client_connected = False + server_connected = False + + counter = 0 + while counter < 100 or not (client_connected and server_connected): + event = self.client.service(0) + if event.type == enet.EVENT_TYPE_CONNECT: + self.assertEquals(event.peer.state, enet.PEER_STATE_CONNECTED) + client_connected = True + event = self.server.service(0) + if event.type == enet.EVENT_TYPE_CONNECT: + self.assertEquals(event.peer.state, enet.PEER_STATE_CONNECTED) + server_connected = True + counter += 1 + + self.assertEquals(client_connected, True) + self.assertEquals(server_connected, True) + + def test_broadcast(self): + broadcast_done = False + broadcast_msg = b"foo\0bar\n baz!" + + while not broadcast_done: + event = self.client.service(0) + if event.type == enet.EVENT_TYPE_RECEIVE: + self.assertEquals(event.peer.state, enet.PEER_STATE_CONNECTED) + self.assertEquals(event.packet.data, broadcast_msg) + broadcast_done = True + event = self.server.service(0) + if event.type == enet.EVENT_TYPE_CONNECT: + self.assertEquals(event.peer.state, enet.PEER_STATE_CONNECTED) + self.server.broadcast(0, enet.Packet(broadcast_msg)) + +class TestPeer(unittest.TestCase): + def setUp(self): + self.client = enet.Host(None, 1, 0, 0, 0) + self.server = enet.Host(enet.Address("localhost", 54301), 1, 0, 0, 0) + self.peer = self.client.connect(enet.Address("localhost", 54301), 1) + self.assertEquals(self.peer.state, enet.PEER_STATE_CONNECTING) + self.assertTrue(self.peer == self.peer) + self.assertFalse(self.peer != self.peer) + + def tearDown(self): + del self.client + del self.server + del self.peer + + def test_access(self): + self.assertRaises(MemoryError, enet.Peer().reset) + + def test_send(self): + msg = b"foo\0bar" + msg_received = False + + while not msg_received: + event = self.server.service(0) + if event.type == enet.EVENT_TYPE_RECEIVE: + msg_received = True + self.assertEquals(event.packet.data, msg) + + event = self.client.service(0) + if event.type == enet.EVENT_TYPE_CONNECT: + packet = enet.Packet(msg) + self.assertEquals(packet.sent, False) + ret = self.peer.send(0, packet) + self.assertEquals(ret, 0) + self.assertEquals(packet.sent, True) + + def test_reset(self): + reset_done = False + while not reset_done: + event = self.server.service(0) + if event.type == enet.EVENT_TYPE_CONNECT: + self.assertEquals(event.peer.state, enet.PEER_STATE_CONNECTED) + event.peer.reset() + self.assertEquals(event.peer.state, enet.PEER_STATE_DISCONNECTED) + reset_done = True + event = self.client.service(0) + + def test_disconnect(self): + connected = True + while connected: + event = self.server.service(0) + if event.type == enet.EVENT_TYPE_DISCONNECT: + self.assertEquals(event.peer.state, enet.PEER_STATE_DISCONNECTED) + connected = False + elif event.type == enet.EVENT_TYPE_CONNECT: + self.assertEquals(self.peer.state, enet.PEER_STATE_CONNECTED) + self.peer.disconnect() + self.assertEquals(self.peer.state, enet.PEER_STATE_DISCONNECTING) + + event = self.client.service(0) + if event.type == enet.EVENT_TYPE_CONNECT: + self.assertEquals(event.peer.state, enet.PEER_STATE_CONNECTED) + + def test_disconnect_later(self): + connected = True + while connected: + event = self.server.service(0) + if event.type == enet.EVENT_TYPE_DISCONNECT: + self.assertEquals(event.peer.state, enet.PEER_STATE_DISCONNECTED) + connected = False + elif event.type == enet.EVENT_TYPE_CONNECT: + self.assertEquals(self.peer.state, enet.PEER_STATE_CONNECTED) + self.peer.disconnect_later() + self.assertEquals(self.peer.state, enet.PEER_STATE_DISCONNECT_LATER) + + event = self.client.service(0) + if event.type == enet.EVENT_TYPE_CONNECT: + self.assertEquals(event.peer.state, enet.PEER_STATE_CONNECTED) + +if __name__ == '__main__': + unittest.main() diff --git a/test_server.py b/test_server.py new file mode 100644 index 0000000..f147553 --- /dev/null +++ b/test_server.py @@ -0,0 +1,29 @@ +import enet + +SHUTDOWN_MSG = "SHUTDOWN" + +host = enet.Host(enet.Address(b"localhost", 54301), 10, 0, 0, 0) + +connect_count = 0 +run = True +shutdown_recv = False +while run: + # Wait 1 second for an event + event = host.service(1000) + if event.type == enet.EVENT_TYPE_CONNECT: + print("%s: CONNECT" % event.peer.address) + connect_count += 1 + elif event.type == enet.EVENT_TYPE_DISCONNECT: + print("%s: DISCONNECT" % event.peer.address) + connect_count -= 1 + if connect_count <= 0 and shutdown_recv: + run = False + elif event.type == enet.EVENT_TYPE_RECEIVE: + print("%s: IN: %r" % (event.peer.address, event.packet.data)) + msg = event.packet.data + if event.peer.send(0, enet.Packet(msg)) < 0: + print("%s: Error sending echo packet!" % event.peer.address) + else: + print("%s: OUT: %r" % (event.peer.address, msg)) + if event.packet.data == "SHUTDOWN": + shutdown_recv = True -- cgit v1.2.3