1
0
This commit is contained in:
2026-01-06 21:24:47 +08:00
parent 6f4d23868c
commit 77cc14fa49
8 changed files with 148 additions and 100 deletions

View File

@@ -1,77 +1,68 @@
#include "command_client.hpp"
#include <basalt/char_types.hpp>
#include <stdexcept>
namespace Basalt::Presenter {
CommandClient::CommandClient(const std::basic_string_view<BSCHAR> pipe_name)
: m_PipeOperator(pipe_name) {
}
CommandClient::CommandClient() : m_PipeOperator(BSTEXT("ed0e3f1f-d214-4880-9562-640bce15e72e")), m_Status(ClientStatus::Ready) {}
CommandClient::~CommandClient() {
}
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");
HandshakePayload CommandClient::WaitHandshake() {
if (m_Status != ClientStatus::Ready) {
throw std::runtime_error("unexcpected client status");
}
// 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)));
ProtocolCode request_code;
m_PipeOperator.Read(&request_code, sizeof(request_code));
if (request_code != ProtocolCode::HANDSHAKE_REQUEST) {
throw std::runtime_error("unexpcted handshake code");
}
// Accept payload
HandshakePayload handshake_payload;
m_PipeOperator.Read(&handshake_payload, sizeof(HandshakePayload));
// Send handshake response (code 0x62) back to Trainer
std::uint8_t handshake_response = HANDSHAKE_CODE_RESPONSE;
m_PipeOperator.Write(&handshake_response, sizeof(handshake_response));
ProtocolCode response_code = ProtocolCode::HANDSHAKE_RESPONSE;
m_PipeOperator.Write(&response_code, sizeof(response_code));
// 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;
// Set status and return
m_Status = ClientStatus::Running;
return handshake_payload;
}
bool CommandClient::Tick(bool actively_stop) {
if (!m_Handshaked) {
throw std::runtime_error("Handshake must be completed before calling Tick");
if (m_Status != ClientStatus::Running) {
throw std::runtime_error("unexcpected client status");
}
// If actively_stop is true, send actively stop code to Trainer
// If actively stop, send actively stop code to Trainer first
if (actively_stop) {
std::uint8_t stop_code = ACTIVELY_STOP_CODE;
m_PipeOperator.Write(&stop_code, sizeof(stop_code));
ProtocolCode sent_code = ProtocolCode::STOP_REQUEST;
m_PipeOperator.Write(&sent_code, sizeof(sent_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));
ProtocolCode sent_code = ProtocolCode::DATA_READY;
m_PipeOperator.Write(&sent_code, sizeof(sent_code));
// Wait for response from Trainer
std::uint8_t received_code;
m_PipeOperator.Read(&received_code, sizeof(received_code));
// Process the response from Trainer
while (true) {
ProtocolCode recv_code;
m_PipeOperator.Read(&recv_code, sizeof(recv_code));
// Handle the received code
if (received_code == DATA_RECEIVED_CODE) {
switch (recv_code) {
case ProtocolCode::DATA_RECEIVED:
// Normal response, continue processing
return false; // Not stopping
} else if (received_code == STOP_CODE) {
case Basalt::Presenter::ProtocolCode::STOP:
// Trainer wants to stop
m_Status = ClientStatus::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");
default:
throw std::runtime_error("unexpected code when running");
}
}
}

View File

@@ -1,38 +1,56 @@
#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)
enum class ProtocolCode : std::uint8_t {
HANDSHAKE_REQUEST = 0x61, //< Trainer -> Presenter
HANDSHAKE_RESPONSE = 0x62, //< Presenter -> Trainer
DATA_READY = 0x01, //< Presenter -> Trainer
DATA_RECEIVED = 0x02, //< Trainer -> Presenter
STOP_REQUEST = 0x71, //< Both directions
STOP = 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
enum class PixelKind : std::uint8_t {
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
};
CommandClient(const std::basic_string_view<BSCHAR> pipe_name = BSTEXT("ed0e3f1f-d214-4880-9562-640bce15e72e"));
#pragma pack(push, 1)
struct HandshakePayload {
PixelKind pixel_kind;
std::uint32_t width;
std::uint32_t height;
};
#pragma pack(pop)
// Status
enum class ClientStatus {
Ready,
Running,
Stop,
};
class CommandClient {
public:
CommandClient();
~CommandClient();
public:
// 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);
HandshakePayload WaitHandshake();
// 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
Shared::PipeOperator m_PipeOperator;
ClientStatus m_Status;
};
} // namespace Basalt::Presenter

View File

@@ -1,4 +1,5 @@
#include "dll_loader.hpp"
#include "command_client.hpp"
#include <basalt/char_types.hpp>
#include <basalt/kernel.hpp>
@@ -8,14 +9,20 @@ namespace Kernel = ::Basalt::Shared::Kernel;
int main(int argc, char* argv[]) {
auto engine_dll = Presenter::DllLoader(Presenter::DllKind::Engine, BSTEXT("BasaltDirectX11Engine"));
auto deliver_dll = Presenter::DllLoader(Presenter::DllKind::Deliver, BSTEXT("BasaltPipeDeliver"));
auto client = Presenter::CommandClient();
auto payload = client.WaitHandshake();
auto* engine = engine_dll.CreateInstance<Kernel::IEngine>();
auto* deliver = deliver_dll.CreateInstance<Kernel::IDeliver>();
Kernel::EngineConfig engine_config{.headless = false, .title = BSTEXT("Fuck You"), .width = 800, .height = 600};
Kernel::EngineConfig engine_config{.headless = false, .title = BSTEXT("Fuck You"), .width = payload.width, .height = payload.height};
engine->Startup(std::move(engine_config));
while (true) {
if (engine->Tick()) break;
auto req_stop = engine->Tick();
auto can_stop = client.Tick(req_stop);
if (can_stop) break;
}
engine->Shutdown();

View File

@@ -1,3 +1,4 @@
#pragma once
#include "char_types.hpp"
#include <string_view>

View File

@@ -1,11 +1,12 @@
import struct
from enum import IntEnum, Enum, auto
from dataclasses import dataclass
from typing import Tuple, Optional
from typing import Callable
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
@@ -14,18 +15,21 @@ class ProtocolCode(IntEnum):
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:
class HandshakePayload:
pixel_kind: PixelKind
width: int
height: int
class ServerStatus(Enum):
Ready = auto()
Running = auto()
@@ -33,7 +37,8 @@ class ServerStatus(Enum):
CODE_PACKER: struct.Struct = struct.Struct("=B")
HANDSHAKE_RESPONSE_PACKER: struct.Struct = struct.Struct("=BII")
HANDSHAKE_REQUEST_PACKER: struct.Struct = struct.Struct("=BII")
class CommandServer:
"""
@@ -41,53 +46,56 @@ class CommandServer:
"""
pipe_operator: PipeOperator
statue: ServerStatus
status: ServerStatus
def __init__(self):
self.pipe_operator = PipeOperator(PIPE_NAME)
self.handshaked = ServerStatus.Ready
self.status = ServerStatus.Ready
def __del__(self):
"""Cleanup resources when object is destroyed."""
self.pipe_operator.close()
def wait_handshake(self) -> ImageProperties:
def wait_handshake(self, payload: HandshakePayload) -> None:
"""
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:
if self.status != ServerStatus.Ready:
raise RuntimeError("unexpected server status")
# Send handshake request to Presenter (code 0x61)
self.pipe_operator.write(CODE_PACKER.pack(ProtocolCode.HANDSHAKE_REQUEST))
self.pipe_operator.write(
CODE_PACKER.pack(ProtocolCode.HANDSHAKE_REQUEST))
# And the payload data
self.pipe_operator.write(
HANDSHAKE_REQUEST_PACKER.pack(payload.pixel_kind, payload.width,
payload.height))
# 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)
raise RuntimeError(
"expect handshake response code, but got another")
# Set status and return
self.statue = ServerStatus.Running
return ImageProperties(PixelKind(raw_pixel_kind), width, height)
self.status = ServerStatus.Running
return
def tick(self, request_stop: bool) -> bool:
def tick(self, data_receiver: Callable[[], None],
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:
if self.status != 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
self.status = ServerStatus.Stop
return True
# Wait for code from Presenter
@@ -98,14 +106,15 @@ class CommandServer:
match ProtocolCode(code):
case ProtocolCode.DATA_READY:
# Receive data
print('Data received')
data_receiver()
# Send data received symbol
self.pipe_operator.write(CODE_PACKER.pack(ProtocolCode.DATA_RECEIVED))
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
self.status = ServerStatus.Stop
return True
case _:
raise RuntimeError("unexpected protocol code when running")

View File

@@ -1,6 +1,31 @@
from command_server import CommandServer, HandshakePayload, PixelKind
import logging
def receive_data() -> None:
logging.info('Data received')
def main():
print("Hello from basalt-trainer!")
server = CommandServer()
print(
'Please launch BasaltPresenter now and then press Enter to continue.')
input()
logging.info('Waiting BasaltPresenter...')
server.wait_handshake(HandshakePayload(PixelKind.GRAY_U8, 600, 600))
logging.info('Start to running.')
while True:
if server.tick(receive_data, False):
break
logging.info('Program stop.')
if __name__ == "__main__":
logging.basicConfig(level=logging.DEBUG,
format='[%(asctime)s] [%(levelname)s] %(message)s',
datefmt='%Y-%m-%d %H:%M:%S')
main()

View File

@@ -45,9 +45,6 @@ class PipeOperator:
)
if self.pipe_handle == win32file.INVALID_HANDLE_VALUE:
raise RuntimeError("Failed to create named pipe.")
# Wait for client to connect
win32pipe.ConnectNamedPipe(self.pipe_handle, None)
else:
# POSIX implementation
self.pipe_name = f"/tmp/{name}"
@@ -158,4 +155,3 @@ class PipeOperator:
total_written += bytes_written
except OSError as e:
raise RuntimeError(f"Failed to write to named pipe: {e}")

View File

@@ -29,8 +29,9 @@ At the beginning of execution,
Trainer send handshake code to Presenter first and then Presenter send another handshake code to Trainer back.
After this, both 2 applications start running.
When Presenter send handshake code back,
Presenter should attach some values following it to indicate some essential properties of data which will be passed to Trainer in future.
When Trainer send handshake code,
Trainer should attach some values following it to indicate some essential properties of data
which instruct Representer how to generate data.
There is a table introduce these properties:
|Data Type|Comment|