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
A python implementation of the Basic Photometric Stereo Algorithm

Photometric-Stereo A python implementation of the Basic Photometric Stereo Algorithm Result Usage run Photometric_Stereo.py Code Tree |data #原始数据,tga格

20 Dec 19, 2022
My own Unicode compression algorithm

Zee Code ZCode is a custom compression algorithm I originally developed for a competition held for the Spring 2019 Datastructures and Algorithms cours

Vahid Zehtab 2 Oct 20, 2021
A litle algorithm that i made for transform a picture in a spreadsheet.

PicsToSheets How it works? It is an algorithm designed to transform an image into a spreadsheet file. this converts image pixels to color cells of she

Guilherme de Oliveira 1 Nov 12, 2021
How on earth can I ever think of a solution like that in an interview?!

fuck-coding-interviews This repository is created by an awkward programmer who always struggles with coding problems on LeetCode, even with some Easy

Vinta Chen 613 Jan 08, 2023
Parameterising Simulated Annealing for the Travelling Salesman Problem

Parameterising Simulated Annealing for the Travelling Salesman Problem Abstract The Travelling Salesman Problem is a well known NP-Hard problem. Given

Gary Sun 55 Jun 15, 2022
This repository provides some codes to demonstrate several variants of Markov-Chain-Monte-Carlo (MCMC) Algorithms.

Demo-of-MCMC These files are based on the class materials of AEROSP 567 taught by Prof. Alex Gorodetsky at University of Michigan. Author: Hung-Hsiang

Sean 1 Feb 05, 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
Python based framework providing a simple and intuitive framework for algorithmic trading

Harvest is a Python based framework providing a simple and intuitive framework for algorithmic trading. Visit Harvest's website for details, tutorials

100 Jan 03, 2023
Algorithm and Structured Programming course project for the first semester of the Internet Systems course at IFPB

Algorithm and Structured Programming course project for the first semester of the Internet Systems course at IFPB

Gabriel Macaúbas 3 May 21, 2022
Infomap is a network clustering algorithm based on the Map equation.

Infomap Infomap is a network clustering algorithm based on the Map equation. For detailed documentation, see mapequation.org/infomap. For a list of re

347 Dec 23, 2022
A Python description of the Kinematic Bicycle Model with an animated example.

Kinematic Bicycle Model Abstract A python library for the Kinematic Bicycle model. The Kinematic Bicycle is a compromise between the non-linear and li

Winston H. 36 Dec 23, 2022
This python algorithm creates a simple house floor plan based on a user-provided CSV file.

This python algorithm creates a simple house floor plan based on a user-provided CSV file. The algorithm generates possible router placements and evaluates where a signal will be reached in every roo

Joshua Miller 1 Nov 12, 2021
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
All algorithms implemented in Python for education

The Algorithms - Python All algorithms implemented in Python - for education Implementations are for learning purposes only. As they may be less effic

1 Oct 20, 2021
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
A priority of preferences for teacher assignment problem

Genetic-Algorithm-for-Assignment-Problem A priority of preferences for teacher assignment problem Keywords k-partition; clustering; education 4.0 Abst

hades 2 Oct 31, 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
Algorithms implemented in Python

Python Algorithms Library Laurent Luce Description The purpose of this library is to help you with common algorithms like: A* path finding. String Mat

Laurent Luce 264 Dec 06, 2022
This repository is an individual project made at BME with the topic of self-driving car simulator and control algorithm.

BME individual project - NEAT based self-driving car This repository is an individual project made at BME with the topic of self-driving car simulator

NGO ANH TUAN 1 Dec 13, 2021
8-puzzle-solver with UCS, ILS, IDA* algorithm

Eight Puzzle 8-puzzle-solver with UCS, ILS, IDA* algorithm pre-usage requirements python3 python3-pip virtualenv prepare enviroment virtualenv -p pyth

Mohsen Arzani 4 Sep 22, 2021