1
0

write shit

This commit is contained in:
2026-01-06 19:50:45 +08:00
parent 4cdc56a32d
commit 6f4d23868c
6 changed files with 252 additions and 48 deletions

View File

@@ -3,12 +3,14 @@ target_sources(BasaltPresenter
PRIVATE
main.cpp
dll_loader.cpp
command_client.cpp
)
target_sources(BasaltPresenter
PUBLIC
FILE_SET HEADERS
FILES
dll_loader.hpp
command_client.hpp
)
target_include_directories(BasaltPresenter
PUBLIC

View File

@@ -0,0 +1,78 @@
#include "command_client.hpp"
#include <stdexcept>
namespace Basalt::Presenter {
CommandClient::CommandClient(const std::basic_string_view<BSCHAR> pipe_name)
: m_PipeOperator(pipe_name) {
}
CommandClient::~CommandClient() {
}
void CommandClient::WaitHandshake(std::uint8_t pixel_kind, std::uint32_t width, std::uint32_t height) {
if (m_Handshaked) {
throw std::runtime_error("Handshake already completed");
}
// Wait for handshake request from Trainer (code 0x61)
std::uint8_t received_code;
m_PipeOperator.Read(&received_code, sizeof(received_code));
if (received_code != HANDSHAKE_CODE_REQUEST) {
throw std::runtime_error("Expected handshake code 0x61, got 0x" +
std::to_string(static_cast<int>(received_code)));
}
// Send handshake response (code 0x62) back to Trainer
std::uint8_t handshake_response = HANDSHAKE_CODE_RESPONSE;
m_PipeOperator.Write(&handshake_response, sizeof(handshake_response));
// Send data properties after handshake
m_PipeOperator.Write(&pixel_kind, sizeof(pixel_kind));
m_PipeOperator.Write(&width, sizeof(width));
m_PipeOperator.Write(&height, sizeof(height));
m_Handshaked = true;
}
bool CommandClient::Tick(bool actively_stop) {
if (!m_Handshaked) {
throw std::runtime_error("Handshake must be completed before calling Tick");
}
// If actively_stop is true, send actively stop code to Trainer
if (actively_stop) {
std::uint8_t stop_code = ACTIVELY_STOP_CODE;
m_PipeOperator.Write(&stop_code, sizeof(stop_code));
}
// Send data ready code to Trainer
std::uint8_t data_ready_code = DATA_READY_CODE;
m_PipeOperator.Write(&data_ready_code, sizeof(data_ready_code));
// Wait for response from Trainer
std::uint8_t received_code;
m_PipeOperator.Read(&received_code, sizeof(received_code));
// Handle the received code
if (received_code == DATA_RECEIVED_CODE) {
// Normal response, continue processing
return false; // Not stopping
} else if (received_code == STOP_CODE) {
// Trainer wants to stop
return true; // Should stop
} else if (received_code == HANDSHAKE_CODE_REQUEST || received_code == HANDSHAKE_CODE_RESPONSE) {
// Unexpected handshake code during Tick
throw std::runtime_error("Unexpected handshake code 0x" +
std::to_string(static_cast<int>(received_code)) +
" received during Tick operation");
} else {
// Unknown code
throw std::runtime_error("Unknown code 0x" +
std::to_string(static_cast<int>(received_code)) +
" received during Tick operation");
}
}
} // namespace Basalt::Presenter

View File

@@ -0,0 +1,38 @@
#pragma once
#include <basalt/pipe_operator.hpp>
#include <cstdint>
namespace Basalt::Presenter {
class CommandClient {
public:
// Protocol codes
static constexpr std::uint8_t HANDSHAKE_CODE_REQUEST = 0x61; // Trainer -> Presenter
static constexpr std::uint8_t HANDSHAKE_CODE_RESPONSE = 0x62; // Presenter -> Trainer
static constexpr std::uint8_t DATA_READY_CODE = 0x01; // Presenter -> Trainer
static constexpr std::uint8_t DATA_RECEIVED_CODE = 0x02; // Trainer -> Presenter
static constexpr std::uint8_t ACTIVELY_STOP_CODE = 0x71; // Both directions
static constexpr std::uint8_t STOP_CODE = 0x71; // Both directions (same code)
// Pixel kind values
static constexpr std::uint8_t PIXEL_GRAY_FLOAT32 = 0x01; // Grayscale represented by one float32
static constexpr std::uint8_t PIXEL_GRAY_U8 = 0x02; // Grayscale represented by one u8
static constexpr std::uint8_t PIXEL_RGB_FLOAT32 = 0x03; // RGB represented by three float32
static constexpr std::uint8_t PIXEL_RGB_U8 = 0x04; // RGB represented by three u8
CommandClient(const std::basic_string_view<BSCHAR> pipe_name = BSTEXT("ed0e3f1f-d214-4880-9562-640bce15e72e"));
~CommandClient();
// Wait for handshake from Trainer, send response with data properties
void WaitHandshake(std::uint8_t pixel_kind, std::uint32_t width, std::uint32_t height);
// Tick function called every frame to send data ready and wait for response
bool Tick(bool actively_stop = false);
private:
Basalt::Shared::PipeOperator m_PipeOperator;
bool m_Handshaked = false; // Track handshake state
};
} // namespace Basalt::Presenter

View File

@@ -0,0 +1,113 @@
import struct
from enum import IntEnum, Enum, auto
from dataclasses import dataclass
from typing import Tuple, Optional
from pipe_operator import PipeOperator
PIPE_NAME: str = "ed0e3f1f-d214-4880-9562-640bce15e72e"
class ProtocolCode(IntEnum):
HANDSHAKE_REQUEST = 0x61 # Trainer -> Presenter
HANDSHAKE_RESPONSE = 0x62 # Presenter -> Trainer
DATA_READY = 0x01 # Presenter -> Trainer
DATA_RECEIVED = 0x02 # Trainer -> Presenter
REQUEST_STOP = 0x71 # Presenter -> Trainer (request stop)
STOP = 0x71 # Trainer -> Presenter (confirm stop)
class PixelKind(IntEnum):
GRAY_FLOAT32 = 0x01 # Grayscale represented by one float32
GRAY_U8 = 0x02 # Grayscale represented by one u8
RGB_FLOAT32 = 0x03 # RGB represented by three float32
RGB_U8 = 0x04 # RGB represented by three u8
@dataclass
class ImageProperties:
pixel_kind: PixelKind
width: int
height: int
class ServerStatus(Enum):
Ready = auto()
Running = auto()
Stop = auto()
CODE_PACKER: struct.Struct = struct.Struct("=B")
HANDSHAKE_RESPONSE_PACKER: struct.Struct = struct.Struct("=BII")
class CommandServer:
"""
Command server implementation for the Trainer side according to the protocol.
"""
pipe_operator: PipeOperator
statue: ServerStatus
def __init__(self):
self.pipe_operator = PipeOperator(PIPE_NAME)
self.handshaked = ServerStatus.Ready
def __del__(self):
"""Cleanup resources when object is destroyed."""
self.pipe_operator.close()
def wait_handshake(self) -> ImageProperties:
"""
Wait for handshake from Presenter, send request first and wait for response with data properties.
Returns a tuple of (pixel_kind, width, height) from the Presenter.
"""
if self.statue != ServerStatus.Ready:
raise RuntimeError("unexpected server status")
# Send handshake request to Presenter (code 0x61)
self.pipe_operator.write(CODE_PACKER.pack(ProtocolCode.HANDSHAKE_REQUEST))
# Wait for handshake response from Presenter (code 0x62)
code_bytes = self.pipe_operator.read(CODE_PACKER.size)
(code, ) = CODE_PACKER.unpack(code_bytes)
if ProtocolCode(code) != ProtocolCode.HANDSHAKE_RESPONSE:
raise RuntimeError("expect handshake response code, but got another")
# Read data properties from Presenter (pixel_kind, width, height)
handshake_response_payload = self.pipe_operator.read(HANDSHAKE_RESPONSE_PACKER.size)
(raw_pixel_kind, width, height) = HANDSHAKE_RESPONSE_PACKER.unpack(handshake_response_payload)
# Set status and return
self.statue = ServerStatus.Running
return ImageProperties(PixelKind(raw_pixel_kind), width, height)
def tick(self, request_stop: bool) -> bool:
"""
Tick function called every frame to wait for data ready from Presenter and send response.
Returns True if a stop code was received (meaning the process should stop), False otherwise.
"""
if self.statue != ServerStatus.Running:
raise RuntimeError("unexpected server status")
# If there is stop requested, we post it first and return
if request_stop:
self.pipe_operator.write(CODE_PACKER.pack(ProtocolCode.STOP))
self.statue = ServerStatus.Stop
return True
# Wait for code from Presenter
code_bytes = self.pipe_operator.read(CODE_PACKER.size)
(code, ) = CODE_PACKER.unpack(code_bytes)
# Analyse code
match ProtocolCode(code):
case ProtocolCode.DATA_READY:
# Receive data
print('Data received')
# Send data received symbol
self.pipe_operator.write(CODE_PACKER.pack(ProtocolCode.DATA_RECEIVED))
case ProtocolCode.REQUEST_STOP:
# Presenter requested stop.
# Agree with it, send code and return.
self.pipe_operator.write(CODE_PACKER.pack(ProtocolCode.STOP))
self.statue = ServerStatus.Stop
return True
case _:
raise RuntimeError("unexpected protocol code when running")
return False

View File

@@ -51,6 +51,10 @@ class PipeOperator:
else:
# POSIX implementation
self.pipe_name = f"/tmp/{name}"
# If there is an existing FIFO file, remove it
if os.path.exists(self.pipe_name):
os.unlink(self.pipe_name)
# Create pipe
try:
os.mkfifo(self.pipe_name)
except OSError as e:
@@ -155,49 +159,3 @@ class PipeOperator:
except OSError as e:
raise RuntimeError(f"Failed to write to named pipe: {e}")
# class PipeServer:
# """
# A convenience class for creating a pipe server that can handle connections in a separate thread.
# """
# def __init__(self, name: str):
# self._name = name
# self._pipe_operator: Optional[PipeOperator] = None
# self._server_thread: Optional[threading.Thread] = None
# self._stop_event = threading.Event()
# def start_server(self, handler_func):
# """
# Start the pipe server in a separate thread.
# :param handler_func: Function to handle the connected pipe (takes PipeOperator as parameter)
# """
# self._server_thread = threading.Thread(target=self._server_worker,
# args=(handler_func, ))
# self._server_thread.start()
# def _server_worker(self, handler_func):
# """Internal method to handle server operations."""
# try:
# # Create pipe server
# pipe_op = PipeOperator(self._name, is_server=True)
# self._pipe_operator = pipe_op
# # Call the handler function with the connected pipe
# handler_func(pipe_op)
# except Exception as e:
# print(f"Error in pipe server: {e}")
# finally:
# if self._pipe_operator:
# self._pipe_operator.close()
# def stop_server(self):
# """Stop the pipe server."""
# self._stop_event.set()
# if self._server_thread:
# self._server_thread.join()
# def get_pipe(self) -> Optional[PipeOperator]:
# """Get the connected pipe operator (only valid after client connects)."""
# return self._pipe_operator

View File

@@ -2,11 +2,18 @@
This document introduce the protocol used between Basalt Presenter and Basalt Trainer.
If there is no specific description, the endian of data is **native endian**, and the alignment also is native.
Because all data are transmitted in local machine, so we use native layout.
## Command Protocol
Before introducing command protocol, it would be better to the priniciple,
that Presenter is the slave application, and Trainer is the master application.
Command protocol is tramsmitted by system named pipe.
The name of pipe is `\\.\pipe\ed0e3f1f-d214-4880-9562-640bce15e72e` on Windows
or `/tmp/ed0e3f1f-d214-4880-9562-640bce15e72e` on POSIX.
|Code|Direction|Comment|
|:---|:---|:---|
|`0x61`|Presenter<--Trainer|Handshake code (Are Presenter ready?)|
@@ -36,8 +43,16 @@ For the all possible values of "pixel kind" in above table, there is also a tabl
|Value|Comment|
|:---|:---|
|`0x01`|Grayscale represneted by one float32|
|`0x01`|Grayscale represented by one float32|
|`0x02`|Grayscale represented by one u8|
|`0x03`|RGB represented by three float32|
|`0x04`|RGB represented by three u8|
## Data Protocol
TODO
### Pipe
For this kind deliver, it is tramsmitted by system named pipe.
The name of pipe is `\\.\pipe\ebe2a39d-75de-4cf4-933f-c50284149d63` on Windows
or `/tmp/ebe2a39d-75de-4cf4-933f-c50284149d63` on POSIX.