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 "command_client.hpp"
#include <basalt/char_types.hpp>
#include <stdexcept> #include <stdexcept>
namespace Basalt::Presenter { namespace Basalt::Presenter {
CommandClient::CommandClient(const std::basic_string_view<BSCHAR> pipe_name) CommandClient::CommandClient() : m_PipeOperator(BSTEXT("ed0e3f1f-d214-4880-9562-640bce15e72e")), m_Status(ClientStatus::Ready) {}
: m_PipeOperator(pipe_name) {
}
CommandClient::~CommandClient() { CommandClient::~CommandClient() {}
}
void CommandClient::WaitHandshake(std::uint8_t pixel_kind, std::uint32_t width, std::uint32_t height) { HandshakePayload CommandClient::WaitHandshake() {
if (m_Handshaked) { if (m_Status != ClientStatus::Ready) {
throw std::runtime_error("Handshake already completed"); throw std::runtime_error("unexcpected client status");
} }
// Wait for handshake request from Trainer (code 0x61) // Wait for handshake request from Trainer (code 0x61)
std::uint8_t received_code; ProtocolCode request_code;
m_PipeOperator.Read(&received_code, sizeof(received_code)); m_PipeOperator.Read(&request_code, sizeof(request_code));
if (request_code != ProtocolCode::HANDSHAKE_REQUEST) {
if (received_code != HANDSHAKE_CODE_REQUEST) { throw std::runtime_error("unexpcted handshake code");
throw std::runtime_error("Expected handshake code 0x61, got 0x" +
std::to_string(static_cast<int>(received_code)));
} }
// Accept payload
HandshakePayload handshake_payload;
m_PipeOperator.Read(&handshake_payload, sizeof(HandshakePayload));
// Send handshake response (code 0x62) back to Trainer // Send handshake response (code 0x62) back to Trainer
std::uint8_t handshake_response = HANDSHAKE_CODE_RESPONSE; ProtocolCode response_code = ProtocolCode::HANDSHAKE_RESPONSE;
m_PipeOperator.Write(&handshake_response, sizeof(handshake_response)); m_PipeOperator.Write(&response_code, sizeof(response_code));
// Send data properties after handshake // Set status and return
m_PipeOperator.Write(&pixel_kind, sizeof(pixel_kind)); m_Status = ClientStatus::Running;
m_PipeOperator.Write(&width, sizeof(width)); return handshake_payload;
m_PipeOperator.Write(&height, sizeof(height));
m_Handshaked = true;
} }
bool CommandClient::Tick(bool actively_stop) { bool CommandClient::Tick(bool actively_stop) {
if (!m_Handshaked) { if (m_Status != ClientStatus::Running) {
throw std::runtime_error("Handshake must be completed before calling Tick"); 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) { if (actively_stop) {
std::uint8_t stop_code = ACTIVELY_STOP_CODE; ProtocolCode sent_code = ProtocolCode::STOP_REQUEST;
m_PipeOperator.Write(&stop_code, sizeof(stop_code)); m_PipeOperator.Write(&sent_code, sizeof(sent_code));
} }
// Send data ready code to Trainer // Send data ready code to Trainer
std::uint8_t data_ready_code = DATA_READY_CODE; ProtocolCode sent_code = ProtocolCode::DATA_READY;
m_PipeOperator.Write(&data_ready_code, sizeof(data_ready_code)); m_PipeOperator.Write(&sent_code, sizeof(sent_code));
// Wait for response from Trainer // Process the response from Trainer
std::uint8_t received_code; while (true) {
m_PipeOperator.Read(&received_code, sizeof(received_code)); ProtocolCode recv_code;
m_PipeOperator.Read(&recv_code, sizeof(recv_code));
// Handle the received code switch (recv_code) {
if (received_code == DATA_RECEIVED_CODE) { case ProtocolCode::DATA_RECEIVED:
// Normal response, continue processing // Normal response, continue processing
return false; // Not stopping return false; // Not stopping
} else if (received_code == STOP_CODE) { case Basalt::Presenter::ProtocolCode::STOP:
// Trainer wants to stop // Trainer wants to stop
return true; // Should stop m_Status = ClientStatus::Stop;
} else if (received_code == HANDSHAKE_CODE_REQUEST || received_code == HANDSHAKE_CODE_RESPONSE) { return true; // Should stop
// Unexpected handshake code during Tick default:
throw std::runtime_error("Unexpected handshake code 0x" + throw std::runtime_error("unexpected code when running");
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");
} }
} }

View File

@@ -1,38 +1,56 @@
#pragma once #pragma once
#include <basalt/pipe_operator.hpp> #include <basalt/pipe_operator.hpp>
#include <cstdint> #include <cstdint>
namespace Basalt::Presenter { namespace Basalt::Presenter {
// Protocol codes
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
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
};
#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 { class CommandClient {
public: public:
// Protocol codes CommandClient();
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(); ~CommandClient();
public:
// Wait for handshake from Trainer, send response with data properties // 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 // Tick function called every frame to send data ready and wait for response
bool Tick(bool actively_stop = false); bool Tick(bool actively_stop = false);
private: private:
Basalt::Shared::PipeOperator m_PipeOperator; Shared::PipeOperator m_PipeOperator;
bool m_Handshaked = false; // Track handshake state ClientStatus m_Status;
}; };
} // namespace Basalt::Presenter } // namespace Basalt::Presenter

View File

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

View File

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

View File

@@ -1,11 +1,12 @@
import struct import struct
from enum import IntEnum, Enum, auto from enum import IntEnum, Enum, auto
from dataclasses import dataclass from dataclasses import dataclass
from typing import Tuple, Optional from typing import Callable
from pipe_operator import PipeOperator from pipe_operator import PipeOperator
PIPE_NAME: str = "ed0e3f1f-d214-4880-9562-640bce15e72e" PIPE_NAME: str = "ed0e3f1f-d214-4880-9562-640bce15e72e"
class ProtocolCode(IntEnum): class ProtocolCode(IntEnum):
HANDSHAKE_REQUEST = 0x61 # Trainer -> Presenter HANDSHAKE_REQUEST = 0x61 # Trainer -> Presenter
HANDSHAKE_RESPONSE = 0x62 # Presenter -> Trainer HANDSHAKE_RESPONSE = 0x62 # Presenter -> Trainer
@@ -14,18 +15,21 @@ class ProtocolCode(IntEnum):
REQUEST_STOP = 0x71 # Presenter -> Trainer (request stop) REQUEST_STOP = 0x71 # Presenter -> Trainer (request stop)
STOP = 0x71 # Trainer -> Presenter (confirm stop) STOP = 0x71 # Trainer -> Presenter (confirm stop)
class PixelKind(IntEnum): class PixelKind(IntEnum):
GRAY_FLOAT32 = 0x01 # Grayscale represented by one float32 GRAY_FLOAT32 = 0x01 # Grayscale represented by one float32
GRAY_U8 = 0x02 # Grayscale represented by one u8 GRAY_U8 = 0x02 # Grayscale represented by one u8
RGB_FLOAT32 = 0x03 # RGB represented by three float32 RGB_FLOAT32 = 0x03 # RGB represented by three float32
RGB_U8 = 0x04 # RGB represented by three u8 RGB_U8 = 0x04 # RGB represented by three u8
@dataclass @dataclass
class ImageProperties: class HandshakePayload:
pixel_kind: PixelKind pixel_kind: PixelKind
width: int width: int
height: int height: int
class ServerStatus(Enum): class ServerStatus(Enum):
Ready = auto() Ready = auto()
Running = auto() Running = auto()
@@ -33,7 +37,8 @@ class ServerStatus(Enum):
CODE_PACKER: struct.Struct = struct.Struct("=B") 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: class CommandServer:
""" """
@@ -41,53 +46,56 @@ class CommandServer:
""" """
pipe_operator: PipeOperator pipe_operator: PipeOperator
statue: ServerStatus status: ServerStatus
def __init__(self): def __init__(self):
self.pipe_operator = PipeOperator(PIPE_NAME) self.pipe_operator = PipeOperator(PIPE_NAME)
self.handshaked = ServerStatus.Ready self.status = ServerStatus.Ready
def __del__(self): def __del__(self):
"""Cleanup resources when object is destroyed.""" """Cleanup resources when object is destroyed."""
self.pipe_operator.close() 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. 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. 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") raise RuntimeError("unexpected server status")
# Send handshake request to Presenter (code 0x61) # 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) # Wait for handshake response from Presenter (code 0x62)
code_bytes = self.pipe_operator.read(CODE_PACKER.size) code_bytes = self.pipe_operator.read(CODE_PACKER.size)
(code, ) = CODE_PACKER.unpack(code_bytes) (code, ) = CODE_PACKER.unpack(code_bytes)
if ProtocolCode(code) != ProtocolCode.HANDSHAKE_RESPONSE: if ProtocolCode(code) != ProtocolCode.HANDSHAKE_RESPONSE:
raise RuntimeError("expect handshake response code, but got another") 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 # Set status and return
self.statue = ServerStatus.Running self.status = ServerStatus.Running
return ImageProperties(PixelKind(raw_pixel_kind), width, height) 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. 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. 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") raise RuntimeError("unexpected server status")
# If there is stop requested, we post it first and return # If there is stop requested, we post it first and return
if request_stop: if request_stop:
self.pipe_operator.write(CODE_PACKER.pack(ProtocolCode.STOP)) self.pipe_operator.write(CODE_PACKER.pack(ProtocolCode.STOP))
self.statue = ServerStatus.Stop self.status = ServerStatus.Stop
return True return True
# Wait for code from Presenter # Wait for code from Presenter
@@ -98,14 +106,15 @@ class CommandServer:
match ProtocolCode(code): match ProtocolCode(code):
case ProtocolCode.DATA_READY: case ProtocolCode.DATA_READY:
# Receive data # Receive data
print('Data received') data_receiver()
# Send data received symbol # 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: case ProtocolCode.REQUEST_STOP:
# Presenter requested stop. # Presenter requested stop.
# Agree with it, send code and return. # Agree with it, send code and return.
self.pipe_operator.write(CODE_PACKER.pack(ProtocolCode.STOP)) self.pipe_operator.write(CODE_PACKER.pack(ProtocolCode.STOP))
self.statue = ServerStatus.Stop self.status = ServerStatus.Stop
return True return True
case _: case _:
raise RuntimeError("unexpected protocol code when running") 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(): 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__": if __name__ == "__main__":
logging.basicConfig(level=logging.DEBUG,
format='[%(asctime)s] [%(levelname)s] %(message)s',
datefmt='%Y-%m-%d %H:%M:%S')
main() main()

View File

@@ -45,9 +45,6 @@ class PipeOperator:
) )
if self.pipe_handle == win32file.INVALID_HANDLE_VALUE: if self.pipe_handle == win32file.INVALID_HANDLE_VALUE:
raise RuntimeError("Failed to create named pipe.") raise RuntimeError("Failed to create named pipe.")
# Wait for client to connect
win32pipe.ConnectNamedPipe(self.pipe_handle, None)
else: else:
# POSIX implementation # POSIX implementation
self.pipe_name = f"/tmp/{name}" self.pipe_name = f"/tmp/{name}"
@@ -158,4 +155,3 @@ class PipeOperator:
total_written += bytes_written total_written += bytes_written
except OSError as e: except OSError as e:
raise RuntimeError(f"Failed to write to named pipe: {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. Trainer send handshake code to Presenter first and then Presenter send another handshake code to Trainer back.
After this, both 2 applications start running. After this, both 2 applications start running.
When Presenter send handshake code back, When Trainer send handshake code,
Presenter should attach some values following it to indicate some essential properties of data which will be passed to Trainer in future. 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: There is a table introduce these properties:
|Data Type|Comment| |Data Type|Comment|