The DarkRift2 networking framework written in Python 3

Overview

DarkRiftPy

DarkRiftPy is Darkrift2 written in Python 3. The implementation is fully compatible with the original version. So you can write a client side on Python that connects to a Darkrift2 server written in C# using the original Darkrift2 library, and vice versa.

DarkRiftPy is built on top of asyncio, Python's standard asynchronus I/O library, and provides a convenient high-level async/await API.

Installation

$ python3 -m pip install darkriftpy

Quick usage example

A simple exampls contains two separate scripts client.py and server.py for client and server respectively.

After client is connected to the server the latter waits for a darkrift message with tag 1, which contains a list of int32 integers in the payload. Once the message with tag 1 is received, the server starts to randomly select a value from the given list and sends it back to the client.

client.py:

None: try: async with darkriftpy.connect("127.0.0.1", 4296, 4296) as client: items = [random.randint(MIN_INT32, MAX_INT32) for _ in range(RND_POOL)] writer = darkriftpy.DarkriftWriter() writer.write_int32s(items) await client.send(darkriftpy.DarkriftMessage(1, writer.bytes)) async for message in client: await process_message(message) print("connection has been closed by the server") except ConnectionError: print("failed to connect to the server") if __name__ == "__main__": asyncio.run(main()) ">
import asyncio
import random


import darkriftpy


RND_POOL = 20

MIN_INT32 = (2 ** 31) * -1
MAX_INT32 = 2 ** 31 - 1


async def process_message(message: darkriftpy.DarkriftMessage) -> None:
    if message.tag != 2:
        raise ValueError("wrong message received")

    num = message.get_reader().read_int32()
    print(f"the server chose the number: {num}")


async def main() -> None:
    try:
        async with darkriftpy.connect("127.0.0.1", 4296, 4296) as client:
            items = [random.randint(MIN_INT32, MAX_INT32) for _ in range(RND_POOL)]

            writer = darkriftpy.DarkriftWriter()
            writer.write_int32s(items)

            await client.send(darkriftpy.DarkriftMessage(1, writer.bytes))

            async for message in client:
                await process_message(message)

            print("connection has been closed by the server")

    except ConnectionError:
        print("failed to connect to the server")


if __name__ == "__main__":
    asyncio.run(main())

server.py:

None: async with darkriftpy.serve(handle_client, "127.0.0.1", 4296, 4296) as server: await asyncio.Future() if __name__ == "__main__": asyncio.run(main()) ">
import asyncio
import random


import darkriftpy


async def handle_client(client: darkriftpy.DarkriftClient) -> None:
    message = await client.recv()

    if message.tag != 1:
        raise RuntimeError("wrong client message received")

        client.close()
        await client.wait_closed()
        return

    reader = message.get_reader()
    items = reader.read_int32s()

    while True:
        writer = darkriftpy.DarkriftWriter()
        writer.write_int32(random.choice(items))

        try:
            await client.send(darkriftpy.DarkriftMessage(2, writer.bytes))
        except darkriftpy.ConnectionClosedError:
            print(f"the client({client.connection_id}) has been disconnected")
            await client.wait_closed()
            return

        await asyncio.sleep(1)


async def main() -> None:
    async with darkriftpy.serve(handle_client, "127.0.0.1", 4296, 4296) as server:
        await asyncio.Future()


if __name__ == "__main__":
    asyncio.run(main())

User defined messages

darkriftpy provides a convinient way to create/send/receive user-defined messages. There is a Message class that can be used as a base class for user-defined ones. The Darkrift tag of a user-defined message is defined by passing the keyword tag argument in the class definition:

import darkriftpy

class ChooseMessage(darkriftpy.Message, tag=1):
    ...

For now, the ChooseMessage message contains no payload. Since the ChooseMessage class is implicitly decorated with the @dataclass decorator, the user can define class variables with type annotations which will be automatically deserialized from or serialized to a binary stream using DarkriftReader and DarkriftWriter classes. Only the following native types can be used as a class variable type: str, bytes, bool, float. Since Darkrift2 allows to use types which are not natively available in python, the darkriftpy.types module provides NewType extensions to cover all the required Darkrift2 types.

import darkriftpy
from darkriftpy.types import int32


class ChooseMessage(darkriftpy.Message, tag=1):
    items: list[int32]

As you can see we used the int32 type from the darkriftpy.types module to define 4 byte signed integer. Since the ChooseMessage class is implicitly decorated with the @dataclass decorator and there is no custom constructor, the following constructor will be created automatically: __init__(self, items: lsit[int32])

Therefore, the ChooseMessage class can be instantiated as follows:

import random


import darkriftpy
from darkriftpy.types import int32


MIN_INT32 = (2 ** 31) * -1
MAX_INT32 = 2 ** 31 - 1


class ChooseMessage(darkriftpy.Message, tag=1):
    items: list[int32]


message = ChooseMessage([random.randint(MIN_INT32, MAX_INT32) for _ in range(10)])

# message.items contains a list with 10 int32 integers

Since the darkriftpy.Message is inherited from darkriftpy.DarkriftMessage the user-defined message can be passed as is to the send method of the darkriftpy.DarkriftClient object.

To convert a received darkriftpy.DarkriftMessage message to the user-defined one, the user can do the following:

...

client: darkriftpy.DarkriftClient
message: darkriftpy.DarkriftMessage = await client.recv()

try:
    choose_message = ChooseMessage.read(message.get_reader())
except RuntimeError:
    # failed to parse the received message
    ...

print(choose_message.items)

The darkriftpy package provides the MessageContainer class to simplify the message serialization and de-siarilization.

import darkriftpy
from darkriftpy.types import int32


messages = darkriftpy.MessageContainer()


@messages.add
class ChooseMessage(darkriftpy.Message, tag=1):
    items: list[int32]


@messages.add
class ChoiceMessage(darkriftpy.Message, tag=2):
    item: int32

...

client: darkriftpy.DarkriftClient
message: darkriftpy.DarkriftMessage = await client.recv()

try:
    msg = messages.convert(message)
except RuntimeError:
    # failed to convert the received darkrift message
    # to the user-defined one

if isinstance(msg, ChooseMessage):
    print(msg.items)
elif isinstance(msg, ChoiceMessage):
    print(msg.item)

We used the add method of the MessageContainer class as decorator to add the user-defined class into the message container messages.
The convert method of the MessageContainer class allows us to convert a raw darkrift message to the user-defined specific one.

Using all these we can create a client wrapper that will return already deserialized messages.

from collections.abc import AsyncIterator


import darkriftpy


class Client:
    def __init__(
        self, client: darkriftpy.DarkriftClient, messages: darkriftpy.MessageContainer
    ):
        self._client = client
        self._messages = messages

    async def recv(self) -> darkriftpy.DarkriftMessage:
        message = await self._client.recv()

        try:
            return self._messages.convert(message)
        except RuntimeError:
            # just return the message as is
            pass

        return message

    async def send(self, message: darkriftpy.DarkriftMessage, reliable: bool = True) -> None:
        await self._client.send(message, reliable)

    def __aiter__(self) -> AsyncIterator[darkriftpy.DarkriftMessage]:
        return self

    async def __anext__(self) -> darkriftpy.DarkriftMessage:
        """
        Returns the next message.

        Stop iteration when the connection is closed.

        """
        try:
            return await self.recv()
        except darkrift.ConnectionClosedError:
            raise StopAsyncIteration()

So now we can use the client wrapper to send and receive user specified messages.

Let's update the first example to use all described features.

client.py:

None: if not isinstance(message, ChoiceMessage): raise ValueError("wrong message received") print(f"the server chose the number: {message.item}") async def main(): try: c: darkriftpy.DarkriftClient async with darkriftpy.connect("127.0.0.1", 4296, 4296) as c: client = Client(c, messages) choose_message = ChooseMessage( [random.randint(MIN_INT32, MAX_INT32) for _ in range(RND_POOL)] ) await client.send(choose_message) async for message in client: await process_message(message) print("Connection has been closed by the server") except ConnectionError: print("failed to connect to the server") if __name__ == "__main__": asyncio.run(main()) ">
import asyncio
import random
from collections.abc import AsyncIterator

import darkriftpy
from darkriftpy.types import int32


RND_POOL = 20

MIN_INT32 = (2 ** 31) * -1
MAX_INT32 = 2 ** 31 - 1


messages = darkriftpy.MessageContainer()


@messages.add
class ChooseMessage(darkriftpy.Message, tag=1):
    items: list[int32]


@messages.add
class ChoiceMessage(darkriftpy.Message, tag=2):
    item: int32


class Client:
    def __init__(
        self, client: darkriftpy.DarkriftClient, messages: darkriftpy.MessageContainer
    ):
        self._client = client
        self._messages = messages

    async def recv(self) -> darkriftpy.DarkriftMessage:
        message = await self._client.recv()

        try:
            return self._messages.convert(message)
        except RuntimeError:
            # just return the message as is
            pass

        return message

    async def send(
        self, message: darkriftpy.DarkriftMessage, reliable: bool = True
    ) -> None:
        await self._client.send(message, reliable)

    def __aiter__(self) -> AsyncIterator[darkriftpy.DarkriftMessage]:
        return self

    async def __anext__(self) -> darkriftpy.DarkriftMessage:
        """
        Returns the next message.

        Stop iteration when the connection is closed.

        """
        try:
            return await self.recv()
        except darkrift.ConnectionClosedError:
            raise StopAsyncIteration()


async def process_message(message: darkriftpy.DarkriftMessage) -> None:
    if not isinstance(message, ChoiceMessage):
        raise ValueError("wrong message received")

    print(f"the server chose the number: {message.item}")


async def main():
    try:
        c: darkriftpy.DarkriftClient
        async with darkriftpy.connect("127.0.0.1", 4296, 4296) as c:
            client = Client(c, messages)
            choose_message = ChooseMessage(
                [random.randint(MIN_INT32, MAX_INT32) for _ in range(RND_POOL)]
            )

            await client.send(choose_message)

            async for message in client:
                await process_message(message)

            print("Connection has been closed by the server")

    except ConnectionError:
        print("failed to connect to the server")


if __name__ == "__main__":
    asyncio.run(main())

server.py:

None: client = Client(c, messages) message = await client.recv() if not isinstance(message, ChooseMessage): raise RuntimeError("wrong client message received") c.close() await c.wait_closed() return while True: choice_message = ChoiceMessage(random.choice(message.items)) try: await client.send(choice_message) except darkriftpy.ConnectionClosedError: print(f"the client({c.connection_id}) has been disconnected") await c.wait_closed() return await asyncio.sleep(1) async def main(): async with darkriftpy.serve(handle_client, "127.0.0.1", 4296, 4296) as server: await asyncio.Future() if __name__ == "__main__": asyncio.run(main()) ">
import asyncio
import random
from collections.abc import AsyncIterator

import darkriftpy
from darkriftpy.types import int32


messages = darkriftpy.MessageContainer()


@messages.add
class ChooseMessage(darkriftpy.Message, tag=1):
    items: list[int32]


@messages.add
class ChoiceMessage(darkriftpy.Message, tag=2):
    item: int32


class Client:
    def __init__(
        self, client: darkriftpy.DarkriftClient, messages: darkriftpy.MessageContainer
    ):
        self._client = client
        self._messages = messages

    async def recv(self) -> darkriftpy.DarkriftMessage:
        message = await self._client.recv()

        try:
            return self._messages.convert(message)
        except RuntimeError:
            # just return the message as is
            pass

        return message

    async def send(
        self, message: darkriftpy.DarkriftMessage, reliable: bool = True
    ) -> None:
        await self._client.send(message, reliable)

    def __aiter__(self) -> AsyncIterator[darkriftpy.DarkriftMessage]:
        return self

    async def __anext__(self) -> darkriftpy.DarkriftMessage:
        """
        Returns the next message.

        Stop iteration when the connection is closed.

        """
        try:
            return await self.recv()
        except darkrift.ConnectionClosedError:
            raise StopAsyncIteration()


async def handle_client(c: darkriftpy.DarkriftClient) -> None:
    client = Client(c, messages)

    message = await client.recv()
    if not isinstance(message, ChooseMessage):
        raise RuntimeError("wrong client message received")

        c.close()
        await c.wait_closed()
        return

    while True:
        choice_message = ChoiceMessage(random.choice(message.items))

        try:
            await client.send(choice_message)
        except darkriftpy.ConnectionClosedError:
            print(f"the client({c.connection_id}) has been disconnected")
            await c.wait_closed()
            return

        await asyncio.sleep(1)


async def main():
    async with darkriftpy.serve(handle_client, "127.0.0.1", 4296, 4296) as server:
        await asyncio.Future()


if __name__ == "__main__":
    asyncio.run(main())

TODO

[ ] - Add multiprocessing support to improve performance and scalability (Fork + Multiplexing I/O).
[ ] - Cover the codebase with tests ;).

Owner
Anton Dobryakov
Anton Dobryakov
Ralebel is an interpreted, Haitian Creole programming language that aims to help Haitians by starting with the fundamental algorithm

Ralebel is an interpreted, Haitian Creole programming language that aims to help Haitians by starting with the fundamental algorithm

Lub Lorry Lamysère 5 Dec 01, 2022
A calculator to test numbers against the collatz conjecture

The Collatz Calculator This is an algorithm custom built by Kyle Dickey, used to test numbers against the simple rules of the Collatz Conjecture. Get

Kyle Dickey 2 Jun 14, 2022
🌟 Python algorithm team note for programming competition or coding test

🌟 Python algorithm team note for programming competition or coding test

Seung Hoon Lee 3 Feb 25, 2022
Esse repositório tem como finalidade expor os trabalhos feitos para disciplina de Algoritmos computacionais e estruturais do CEFET-RJ no ano letivo de 2021.

Exercícios de Python 🐍 Esse repositório tem como finalidade expor os trabalhos feitos para disciplina de Algoritmos computacionais e estruturais do C

Rafaela Bezerra de Figueiredo 1 Nov 20, 2021
Implemented page rank program

Page Rank Implemented page rank program based on fact that a website is more important if it is linked to by other important websites using recursive

Vaibhaw 6 Aug 24, 2022
🧬 Performant Evolutionary Algorithms For Python with Ray support

🧬 Performant Evolutionary Algorithms For Python with Ray support

Nathan 49 Oct 20, 2022
Python package to monitor the power consumption of any algorithm

CarbonAI This project aims at creating a python package that allows you to monitor the power consumption of any python function. Documentation The com

Capgemini Invent France 36 Nov 11, 2022
A tictactoe where you never win, implemented using minimax algorithm

Unbeatable_TicTacToe A tictactoe where you never win, implemented using minimax algorithm Requirements Make sure you have the pygame module along with

Jessica Jolly 3 Jul 28, 2022
Evol is clear dsl for composable evolutionary algorithms that optimised for joy.

Evol is clear dsl for composable evolutionary algorithms that optimised for joy. Installation We currently support python3.6 and python3.7 and you can

GoDataDriven 178 Dec 27, 2022
An NUS timetable generator which uses a genetic algorithm to optimise timetables to suit the needs of NUS students.

A timetable optimiser for NUS which uses an evolutionary algorithm to "breed" a timetable suited to your needs.

Nicholas Lee 3 Jan 09, 2022
Algorithms written in different programming languages

Data Structures and Algorithms Clean example implementations of data structures and algorithms written in different languages. List of implementations

Zoran Pandovski 1.3k Jan 03, 2023
Xor encryption and decryption algorithm

Folosire: Pentru encriptare: python encrypt.py parola fișier pentru criptare fișier encriptat(de tip binar) Pentru decriptare: python decrypt.p

2 Dec 05, 2021
Pathfinding visualizer in pygame: A*

Pathfinding Visualizer A* What is this A* algorithm ? Simply put, it is an algorithm that aims to find the shortest possible path between two location

0 Feb 26, 2022
Our implementation of Gillespie's Stochastic Simulation Algorithm (SSA)

SSA Our implementation of Gillespie's Stochastic Simulation Algorithm (SSA) Requirements python =3.7 numpy pandas matplotlib pyyaml Command line usag

Anoop Lab 1 Jan 27, 2022
A simple library for implementing common design patterns.

PyPattyrn from pypattyrn.creational.singleton import Singleton class DummyClass(object, metaclass=Singleton): # DummyClass is now a Singleton!

1.7k Jan 01, 2023
There are some basic arithmatic in Pattern Recognization and Machine Learning writed in Python in this repository

There are some basic arithmatic in Pattern Recognization and Machine Learning writed in Python in this repository

1 Nov 19, 2021
Path finding algorithm visualizer with python

path-finding-algorithm-visualizer ~ click on the grid to place the starting block and then click elsewhere to add the end block ~ click again to place

izumi 1 Oct 31, 2021
This repository explores an implementation of Grover's Algorithm for knights on a chessboard.

Grover Knights Welcome to my Knights project! Project Description: I explore an implementation of a quantum oracle for knights on a chessboard.

Will Sun 8 Feb 22, 2022
A minimal implementation of the IQRM interference flagging algorithm for radio pulsar and transient searches

A minimal implementation of the IQRM interference flagging algorithm for radio pulsar and transient searches. This module only provides the algorithm that infers a channel mask from some spectral sta

Vincent Morello 6 Nov 29, 2022
PathPlanning - Common used path planning algorithms with animations.

Overview This repository implements some common path planning algorithms used in robotics, including Search-based algorithms and Sampling-based algori

Huiming Zhou 5.1k Jan 08, 2023