0.3.4-alpha.2
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
# file: Cargo.toml
|
# file: Cargo.toml
|
||||||
# version: 86
|
# version: 87
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
resolver = "3"
|
resolver = "3"
|
||||||
@@ -13,6 +13,7 @@ members = [
|
|||||||
"crates/apps/game-snake-poc-desktop",
|
"crates/apps/game-snake-poc-desktop",
|
||||||
"crates/common/game-assets-lib",
|
"crates/common/game-assets-lib",
|
||||||
"crates/common/game-logging-lib",
|
"crates/common/game-logging-lib",
|
||||||
|
"crates/common/game-realtime-transport-lib",
|
||||||
"crates/apps/game-android-entrypoint",
|
"crates/apps/game-android-entrypoint",
|
||||||
"crates/apps/game-reflex-poc-tauri",
|
"crates/apps/game-reflex-poc-tauri",
|
||||||
"crates/apps/game-reflex-poc-wasm",
|
"crates/apps/game-reflex-poc-wasm",
|
||||||
@@ -21,7 +22,7 @@ members = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.3.4-alpha.1"
|
version = "0.3.4-alpha.2"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://git.sasedev.com/Sasedev/games"
|
repository = "https://git.sasedev.com/Sasedev/games"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: README.md -->
|
<!-- file: README.md -->
|
||||||
<!-- version: 54 -->
|
<!-- version: 55 -->
|
||||||
|
|
||||||
# games.sasedev
|
# games.sasedev
|
||||||
|
|
||||||
@@ -27,9 +27,9 @@ Workspace expérimental puis productif pour des jeux multiplateformes principale
|
|||||||
|
|
||||||
Version stable de référence : `0.3.3`.
|
Version stable de référence : `0.3.3`.
|
||||||
|
|
||||||
Version candidate active : `0.3.4-alpha.1`. `0.3.2` reste différée.
|
Version candidate active : `0.3.4-alpha.2`. `0.3.2` reste différée.
|
||||||
|
|
||||||
La stable `0.3.3` livre la voie Android SDL3 native multi-ABI : build Gradle/Cargo sans orchestrateur Python, APK Debug universal et AAB Release pour `arm64-v8a`, `armeabi-v7a`, `x86_64` et `x86`, `minSdk 21` réellement fumé et compatibilité pages mémoire 16 KB validée sur les ABI 64 bits. `0.3.4-alpha.1` migre la nomenclature de prerelease et cadre l'API de transport realtime/WebSocket avant toute implémentation réseau lourde.
|
La stable `0.3.3` livre la voie Android SDL3 native multi-ABI : build Gradle/Cargo sans orchestrateur Python, APK Debug universal et AAB Release pour `arm64-v8a`, `armeabi-v7a`, `x86_64` et `x86`, `minSdk 21` réellement fumé et compatibilité pages mémoire 16 KB validée sur les ABI 64 bits. `0.3.4-alpha.2` introduit la première API transport-neutral realtime : messages binaires opaques, fermeture distante explicite, erreurs catégorisées et split statique sender/receiver, sans dépendance Tokio ou WebSocket.
|
||||||
|
|
||||||
Les deux premiers jeux sont des POC structurels : `game-reflex-poc` et `game-snake-poc`. Ils existent d'abord pour valider les frontières du workspace, le moteur, les assets et le packaging multiplateforme.
|
Les deux premiers jeux sont des POC structurels : `game-reflex-poc` et `game-snake-poc`. Ils existent d'abord pour valider les frontières du workspace, le moteur, les assets et le packaging multiplateforme.
|
||||||
|
|
||||||
|
|||||||
14
crates/common/game-realtime-transport-lib/Cargo.toml
Normal file
14
crates/common/game-realtime-transport-lib/Cargo.toml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# file: crates/common/game-realtime-transport-lib/Cargo.toml
|
||||||
|
# version: 1
|
||||||
|
|
||||||
|
[package]
|
||||||
|
name = "game-realtime-transport-lib"
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
repository.workspace = true
|
||||||
|
authors.workspace = true
|
||||||
|
publish.workspace = true
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
42
crates/common/game-realtime-transport-lib/src/connection.rs
Normal file
42
crates/common/game-realtime-transport-lib/src/connection.rs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
// file: crates/common/game-realtime-transport-lib/src/connection.rs
|
||||||
|
// version: 1
|
||||||
|
|
||||||
|
/// Established realtime connection that owns independent send and receive halves.
|
||||||
|
pub trait RealtimeConnection {
|
||||||
|
/// Send half produced when this connection is split.
|
||||||
|
type Sender: crate::RealtimeSender;
|
||||||
|
/// Receive half produced when this connection is split.
|
||||||
|
type Receiver: crate::RealtimeReceiver;
|
||||||
|
|
||||||
|
/// Consumes the connection and returns independent send and receive halves.
|
||||||
|
fn split(self) -> (Self::Sender, Self::Receiver);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send half of an established realtime connection.
|
||||||
|
pub trait RealtimeSender {
|
||||||
|
/// Future returned by [`RealtimeSender::send`].
|
||||||
|
type SendFuture<'a>: core::future::Future<Output = Result<(), crate::TransportError>>
|
||||||
|
where
|
||||||
|
Self: 'a;
|
||||||
|
/// Future returned by [`RealtimeSender::close`].
|
||||||
|
type CloseFuture<'a>: core::future::Future<Output = Result<(), crate::TransportError>>
|
||||||
|
where
|
||||||
|
Self: 'a;
|
||||||
|
|
||||||
|
/// Sends one owned opaque binary message, respecting backend backpressure.
|
||||||
|
fn send(&mut self, message: crate::TransportMessage) -> Self::SendFuture<'_>;
|
||||||
|
|
||||||
|
/// Initiates a clean local transport-level close.
|
||||||
|
fn close(&mut self) -> Self::CloseFuture<'_>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Receive half of an established realtime connection.
|
||||||
|
pub trait RealtimeReceiver {
|
||||||
|
/// Future returned by [`RealtimeReceiver::receive`].
|
||||||
|
type ReceiveFuture<'a>: core::future::Future<Output = Result<crate::TransportReceive, crate::TransportError>>
|
||||||
|
where
|
||||||
|
Self: 'a;
|
||||||
|
|
||||||
|
/// Receives one binary message or observes a clean remote close.
|
||||||
|
fn receive(&mut self) -> Self::ReceiveFuture<'_>;
|
||||||
|
}
|
||||||
82
crates/common/game-realtime-transport-lib/src/error.rs
Normal file
82
crates/common/game-realtime-transport-lib/src/error.rs
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
// file: crates/common/game-realtime-transport-lib/src/error.rs
|
||||||
|
// version: 1
|
||||||
|
|
||||||
|
/// Stable transport-neutral category for a realtime operation failure.
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub enum TransportErrorKind {
|
||||||
|
/// An endpoint or transport configuration is invalid.
|
||||||
|
InvalidConfiguration,
|
||||||
|
/// A client connection could not be established.
|
||||||
|
Connect,
|
||||||
|
/// A server endpoint could not be bound.
|
||||||
|
Bind,
|
||||||
|
/// A server could not accept an incoming connection.
|
||||||
|
Accept,
|
||||||
|
/// An operation exceeded its configured deadline.
|
||||||
|
Timeout,
|
||||||
|
/// A payload exceeds the configured transport bound.
|
||||||
|
MessageTooLarge,
|
||||||
|
/// The transport cannot currently accept more outbound data within its configured bounds.
|
||||||
|
Backpressure,
|
||||||
|
/// The requested operation requires a connection that is already closed.
|
||||||
|
Closed,
|
||||||
|
/// An underlying I/O operation failed.
|
||||||
|
Io,
|
||||||
|
/// The backend reported a protocol-level or transport-specific failure.
|
||||||
|
Protocol,
|
||||||
|
/// The operation was explicitly aborted or cancelled when that distinction is observable.
|
||||||
|
Aborted,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl core::fmt::Display for TransportErrorKind {
|
||||||
|
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||||
|
return match self {
|
||||||
|
crate::TransportErrorKind::InvalidConfiguration => formatter.write_str("invalid configuration"),
|
||||||
|
crate::TransportErrorKind::Connect => formatter.write_str("connect failure"),
|
||||||
|
crate::TransportErrorKind::Bind => formatter.write_str("bind failure"),
|
||||||
|
crate::TransportErrorKind::Accept => formatter.write_str("accept failure"),
|
||||||
|
crate::TransportErrorKind::Timeout => formatter.write_str("operation timed out"),
|
||||||
|
crate::TransportErrorKind::MessageTooLarge => formatter.write_str("message too large"),
|
||||||
|
crate::TransportErrorKind::Backpressure => formatter.write_str("transport backpressure"),
|
||||||
|
crate::TransportErrorKind::Closed => formatter.write_str("connection closed"),
|
||||||
|
crate::TransportErrorKind::Io => formatter.write_str("I/O failure"),
|
||||||
|
crate::TransportErrorKind::Protocol => formatter.write_str("protocol failure"),
|
||||||
|
crate::TransportErrorKind::Aborted => formatter.write_str("operation aborted"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Transport-neutral operation failure with a stable category and diagnostic detail.
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct TransportError {
|
||||||
|
detail: String,
|
||||||
|
kind: crate::TransportErrorKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TransportError {
|
||||||
|
/// Creates a transport failure from a stable category and backend-neutral diagnostic detail.
|
||||||
|
#[must_use]
|
||||||
|
pub fn new(kind: crate::TransportErrorKind, detail: impl Into<String>) -> Self {
|
||||||
|
return Self { detail: detail.into(), kind };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Borrows the diagnostic detail associated with the failure.
|
||||||
|
#[must_use]
|
||||||
|
pub fn detail(&self) -> &str {
|
||||||
|
return self.detail.as_str();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the stable transport-neutral failure category.
|
||||||
|
#[must_use]
|
||||||
|
pub fn kind(&self) -> crate::TransportErrorKind {
|
||||||
|
return self.kind;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl core::fmt::Display for TransportError {
|
||||||
|
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||||
|
return write!(formatter, "{}: {}", self.kind, self.detail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for TransportError {}
|
||||||
31
crates/common/game-realtime-transport-lib/src/lib.rs
Normal file
31
crates/common/game-realtime-transport-lib/src/lib.rs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
// file: crates/common/game-realtime-transport-lib/src/lib.rs
|
||||||
|
// version: 1
|
||||||
|
|
||||||
|
#![warn(missing_docs)]
|
||||||
|
#![deny(unreachable_pub)]
|
||||||
|
#![forbid(unsafe_code)]
|
||||||
|
|
||||||
|
//! Transport-neutral realtime connection contracts for games.sasedev.
|
||||||
|
|
||||||
|
mod connection;
|
||||||
|
mod error;
|
||||||
|
mod message;
|
||||||
|
|
||||||
|
/// Re-export of an established realtime connection that can be split into independent halves.
|
||||||
|
pub use self::connection::RealtimeConnection;
|
||||||
|
/// Re-export of the receive half of an established realtime connection.
|
||||||
|
pub use self::connection::RealtimeReceiver;
|
||||||
|
/// Re-export of the send half of an established realtime connection.
|
||||||
|
pub use self::connection::RealtimeSender;
|
||||||
|
/// Re-export of a transport-neutral operation failure.
|
||||||
|
pub use self::error::TransportError;
|
||||||
|
/// Re-export of transport-neutral error categories.
|
||||||
|
pub use self::error::TransportErrorKind;
|
||||||
|
/// Re-export of an owned opaque binary transport message.
|
||||||
|
pub use self::message::TransportMessage;
|
||||||
|
/// Re-export of the result of one receive operation.
|
||||||
|
pub use self::message::TransportReceive;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "../unit_tests/contract.rs"]
|
||||||
|
mod tests;
|
||||||
49
crates/common/game-realtime-transport-lib/src/message.rs
Normal file
49
crates/common/game-realtime-transport-lib/src/message.rs
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
// file: crates/common/game-realtime-transport-lib/src/message.rs
|
||||||
|
// version: 1
|
||||||
|
|
||||||
|
/// Owned opaque binary payload carried by a realtime transport.
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct TransportMessage {
|
||||||
|
bytes: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TransportMessage {
|
||||||
|
/// Creates a transport message from owned bytes.
|
||||||
|
#[must_use]
|
||||||
|
pub fn new(bytes: Vec<u8>) -> Self {
|
||||||
|
return Self { bytes };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Borrows the opaque payload bytes.
|
||||||
|
#[must_use]
|
||||||
|
pub fn as_bytes(&self) -> &[u8] {
|
||||||
|
return self.bytes.as_slice();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consumes the message and returns its owned payload bytes.
|
||||||
|
#[must_use]
|
||||||
|
pub fn into_bytes(self) -> Vec<u8> {
|
||||||
|
return self.bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns whether the payload is empty.
|
||||||
|
#[must_use]
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
return self.bytes.is_empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the payload length in bytes.
|
||||||
|
#[must_use]
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
return self.bytes.len();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Outcome of one successful transport receive operation.
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub enum TransportReceive {
|
||||||
|
/// One opaque binary message was received.
|
||||||
|
Message(crate::TransportMessage),
|
||||||
|
/// The remote peer completed a clean transport-level close.
|
||||||
|
Closed,
|
||||||
|
}
|
||||||
131
crates/common/game-realtime-transport-lib/unit_tests/contract.rs
Normal file
131
crates/common/game-realtime-transport-lib/unit_tests/contract.rs
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
// file: crates/common/game-realtime-transport-lib/unit_tests/contract.rs
|
||||||
|
// version: 1
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
|
struct TestSender {
|
||||||
|
closed: bool,
|
||||||
|
sent_bytes: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl crate::RealtimeSender for TestSender {
|
||||||
|
type SendFuture<'a>
|
||||||
|
= std::future::Ready<Result<(), crate::TransportError>>
|
||||||
|
where
|
||||||
|
Self: 'a;
|
||||||
|
type CloseFuture<'a>
|
||||||
|
= std::future::Ready<Result<(), crate::TransportError>>
|
||||||
|
where
|
||||||
|
Self: 'a;
|
||||||
|
|
||||||
|
fn send(&mut self, message: crate::TransportMessage) -> Self::SendFuture<'_> {
|
||||||
|
self.sent_bytes = self.sent_bytes.saturating_add(message.len());
|
||||||
|
return std::future::ready(Ok(()));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn close(&mut self) -> Self::CloseFuture<'_> {
|
||||||
|
self.closed = true;
|
||||||
|
return std::future::ready(Ok(()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
|
struct TestReceiver;
|
||||||
|
|
||||||
|
impl crate::RealtimeReceiver for TestReceiver {
|
||||||
|
type ReceiveFuture<'a>
|
||||||
|
= std::future::Ready<Result<crate::TransportReceive, crate::TransportError>>
|
||||||
|
where
|
||||||
|
Self: 'a;
|
||||||
|
|
||||||
|
fn receive(&mut self) -> Self::ReceiveFuture<'_> {
|
||||||
|
return std::future::ready(Ok(crate::TransportReceive::Closed));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TestConnection;
|
||||||
|
|
||||||
|
impl crate::RealtimeConnection for TestConnection {
|
||||||
|
type Sender = TestSender;
|
||||||
|
type Receiver = TestReceiver;
|
||||||
|
|
||||||
|
fn split(self) -> (Self::Sender, Self::Receiver) {
|
||||||
|
return (TestSender { closed: false, sent_bytes: 0 }, TestReceiver);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_ready<T>(future: impl core::future::Future<Output = T>) -> T {
|
||||||
|
let mut future = std::pin::pin!(future);
|
||||||
|
let waker = std::task::Waker::noop();
|
||||||
|
let mut context = std::task::Context::from_waker(waker);
|
||||||
|
return match core::future::Future::poll(future.as_mut(), &mut context) {
|
||||||
|
std::task::Poll::Ready(value) => value,
|
||||||
|
std::task::Poll::Pending => unreachable!("test future unexpectedly remained pending"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn message_preserves_owned_binary_payload() {
|
||||||
|
let message = crate::TransportMessage::new(vec![0, 1, 2, 255]);
|
||||||
|
assert_eq!(message.as_bytes(), &[0, 1, 2, 255]);
|
||||||
|
assert_eq!(message.len(), 4);
|
||||||
|
assert!(!message.is_empty());
|
||||||
|
assert_eq!(message.into_bytes(), vec![0, 1, 2, 255]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty_message_remains_a_valid_transport_payload() {
|
||||||
|
let message = crate::TransportMessage::new(Vec::new());
|
||||||
|
assert!(message.is_empty());
|
||||||
|
assert_eq!(message.len(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn clean_remote_close_is_distinct_from_transport_error() {
|
||||||
|
let mut receiver = TestReceiver;
|
||||||
|
let received = poll_ready(crate::RealtimeReceiver::receive(&mut receiver));
|
||||||
|
assert_eq!(received, Ok(crate::TransportReceive::Closed));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn connection_split_produces_independent_contract_halves() {
|
||||||
|
let (mut sender, mut receiver) = crate::RealtimeConnection::split(TestConnection);
|
||||||
|
assert_eq!(poll_ready(crate::RealtimeSender::send(&mut sender, crate::TransportMessage::new(vec![1, 2, 3]))), Ok(()));
|
||||||
|
assert_eq!(sender.sent_bytes, 3);
|
||||||
|
assert_eq!(poll_ready(crate::RealtimeReceiver::receive(&mut receiver)), Ok(crate::TransportReceive::Closed));
|
||||||
|
assert_eq!(poll_ready(crate::RealtimeSender::close(&mut sender)), Ok(()));
|
||||||
|
assert!(sender.closed);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn message_receive_variant_preserves_payload() {
|
||||||
|
let received = crate::TransportReceive::Message(crate::TransportMessage::new(vec![4, 5, 6]));
|
||||||
|
assert_eq!(received, crate::TransportReceive::Message(crate::TransportMessage::new(vec![4, 5, 6])));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn transport_error_preserves_stable_kind_and_diagnostic_detail() {
|
||||||
|
let error = crate::TransportError::new(crate::TransportErrorKind::Timeout, "send deadline exceeded");
|
||||||
|
assert_eq!(error.kind(), crate::TransportErrorKind::Timeout);
|
||||||
|
assert_eq!(error.detail(), "send deadline exceeded");
|
||||||
|
assert_eq!(error.to_string(), "operation timed out: send deadline exceeded");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn transport_error_kinds_have_stable_human_readable_labels() {
|
||||||
|
let cases = [
|
||||||
|
(crate::TransportErrorKind::InvalidConfiguration, "invalid configuration"),
|
||||||
|
(crate::TransportErrorKind::Connect, "connect failure"),
|
||||||
|
(crate::TransportErrorKind::Bind, "bind failure"),
|
||||||
|
(crate::TransportErrorKind::Accept, "accept failure"),
|
||||||
|
(crate::TransportErrorKind::Timeout, "operation timed out"),
|
||||||
|
(crate::TransportErrorKind::MessageTooLarge, "message too large"),
|
||||||
|
(crate::TransportErrorKind::Backpressure, "transport backpressure"),
|
||||||
|
(crate::TransportErrorKind::Closed, "connection closed"),
|
||||||
|
(crate::TransportErrorKind::Io, "I/O failure"),
|
||||||
|
(crate::TransportErrorKind::Protocol, "protocol failure"),
|
||||||
|
(crate::TransportErrorKind::Aborted, "operation aborted"),
|
||||||
|
];
|
||||||
|
for (kind, expected) in cases {
|
||||||
|
assert_eq!(kind.to_string(), expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
133
deltas/0.3.4/alpha.2.md
Normal file
133
deltas/0.3.4/alpha.2.md
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
<!-- file: deltas/0.3.4/alpha.2.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta 0.3.4-alpha.2
|
||||||
|
|
||||||
|
## Base
|
||||||
|
|
||||||
|
Base : `0.3.4-alpha.1` validée par l'utilisateur le 2026-09-21.
|
||||||
|
|
||||||
|
Cette tranche matérialise uniquement la frontière transport-neutral prévue par le plan `0.3.4`. Elle n'ajoute encore ni Tokio, ni Tungstenite, ni socket, ni protocole de session/gameplay.
|
||||||
|
|
||||||
|
## Historique fermé
|
||||||
|
|
||||||
|
Ajout de :
|
||||||
|
|
||||||
|
```text
|
||||||
|
history/0.3.4/alpha.1.md
|
||||||
|
```
|
||||||
|
|
||||||
|
L'entrée enregistre les gates effectivement fournies par l'utilisateur pour `alpha.1`, notamment le rebuild complet après `cargo clean`, les audits propres, `cargo check --workspace` et Clippy workspace strict.
|
||||||
|
|
||||||
|
## Nouvelle crate `game-realtime-transport-lib`
|
||||||
|
|
||||||
|
Ajout de :
|
||||||
|
|
||||||
|
```text
|
||||||
|
crates/common/game-realtime-transport-lib/Cargo.toml
|
||||||
|
crates/common/game-realtime-transport-lib/src/lib.rs
|
||||||
|
crates/common/game-realtime-transport-lib/src/connection.rs
|
||||||
|
crates/common/game-realtime-transport-lib/src/error.rs
|
||||||
|
crates/common/game-realtime-transport-lib/src/message.rs
|
||||||
|
crates/common/game-realtime-transport-lib/unit_tests/contract.rs
|
||||||
|
```
|
||||||
|
|
||||||
|
La crate appartient à `crates/common/` car le contrat realtime est indépendant d'une génération de moteur et d'un backend réseau concret.
|
||||||
|
|
||||||
|
Elle ne possède aucune dépendance tierce.
|
||||||
|
|
||||||
|
## Contrat public
|
||||||
|
|
||||||
|
Le payload transport est `TransportMessage`, un buffer binaire possédé sans sémantique gameplay ni codec.
|
||||||
|
|
||||||
|
`TransportReceive` distingue explicitement :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Message(TransportMessage)
|
||||||
|
Closed
|
||||||
|
```
|
||||||
|
|
||||||
|
Une fermeture distante propre n'est donc pas confondue avec une erreur I/O.
|
||||||
|
|
||||||
|
Les contrats sont :
|
||||||
|
|
||||||
|
```text
|
||||||
|
RealtimeConnection
|
||||||
|
RealtimeSender
|
||||||
|
RealtimeReceiver
|
||||||
|
```
|
||||||
|
|
||||||
|
`RealtimeConnection::split()` produit les deux moitiés indépendantes. Les opérations `send`, `close` et `receive` restent async sans imposer un runtime particulier.
|
||||||
|
|
||||||
|
L'implémentation du contrat utilise des futures associées GAT. Cette forme évite une allocation `Box<dyn Future>`, conserve le dispatch statique et n'impose pas de borne `Send` au niveau transport-neutral. Un backend natif reste libre de fournir des futures `Send`; un backend navigateur/WASM futur n'est pas exclu artificiellement.
|
||||||
|
|
||||||
|
## Erreurs
|
||||||
|
|
||||||
|
`TransportError` conserve une catégorie stable `TransportErrorKind` et un détail diagnostic backend-neutral.
|
||||||
|
|
||||||
|
Les catégories de baseline sont :
|
||||||
|
|
||||||
|
```text
|
||||||
|
InvalidConfiguration
|
||||||
|
Connect
|
||||||
|
Bind
|
||||||
|
Accept
|
||||||
|
Timeout
|
||||||
|
MessageTooLarge
|
||||||
|
Backpressure
|
||||||
|
Closed
|
||||||
|
Io
|
||||||
|
Protocol
|
||||||
|
Aborted
|
||||||
|
```
|
||||||
|
|
||||||
|
Les futurs types d'erreur Tungstenite ne sont pas exposés par cette crate.
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
Les tests unitaires couvrent :
|
||||||
|
|
||||||
|
- conservation du payload binaire possédé, y compris le payload vide ;
|
||||||
|
- distinction message/fermeture propre ;
|
||||||
|
- split sender/receiver ;
|
||||||
|
- futures associées de send/receive/close avec une implémentation test sans runtime ;
|
||||||
|
- catégorie, détail et libellés des erreurs.
|
||||||
|
|
||||||
|
Aucun test réseau n'existe dans cette tranche puisqu'aucun backend réseau n'existe encore.
|
||||||
|
|
||||||
|
## Documentation locale
|
||||||
|
|
||||||
|
Aucun `README.md` ni `USAGE.md` local n'est ajouté pendant `alpha.2`. La crate est volontairement petite, son contrat est documenté par rustdoc et le plan central couvre encore ses frontières ; créer un guide local maintenant dupliquerait ces sources sans valeur durable supplémentaire. Ce choix sera réévalué pendant la consolidation finale conformément à `DOC-CRATE-001`.
|
||||||
|
|
||||||
|
## Fichiers existants modifiés
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml
|
||||||
|
README.md
|
||||||
|
docs/plans/004-V0_3_4_REALTIME_TRANSPORT_WEBSOCKET_PLAN.md
|
||||||
|
```
|
||||||
|
|
||||||
|
`Cargo.toml` ajoute le membre workspace et passe la version technique à `0.3.4-alpha.2`.
|
||||||
|
|
||||||
|
Le README racine annonce la candidate active. Le plan enregistre la validation de `alpha.1` et ferme le choix des futures associées GAT pour le contrat commun.
|
||||||
|
|
||||||
|
`ROADMAP.md` et `CHANGELOG.md` ne changent pas : le scope macroscopique de `0.3.4` est inchangé et une alpha n'ajoute normalement pas d'entrée de changelog.
|
||||||
|
|
||||||
|
## Validation attendue
|
||||||
|
|
||||||
|
Appliquer d'abord le formatage canonique, puis exécuter :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo fmt --all
|
||||||
|
cargo fmt --all -- --check
|
||||||
|
|
||||||
|
python3 scripts/audit_rust_workspace_rules.py
|
||||||
|
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates Android Web deltas history
|
||||||
|
python3 scripts/audit_distribution_layout.py
|
||||||
|
|
||||||
|
cargo check --workspace
|
||||||
|
cargo clippy --workspace --all-targets --all-features -- -D warnings
|
||||||
|
cargo test -p game-realtime-transport-lib --all-targets --all-features
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucune gate Android, Web/Tauri ou réseau n'est requise : cette tranche n'affecte ni leurs contrats ni un backend socket.
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: docs/plans/004-V0_3_4_REALTIME_TRANSPORT_WEBSOCKET_PLAN.md -->
|
<!-- file: docs/plans/004-V0_3_4_REALTIME_TRANSPORT_WEBSOCKET_PLAN.md -->
|
||||||
<!-- version: 1 -->
|
<!-- version: 2 -->
|
||||||
|
|
||||||
# Plan 0.3.4 — transport realtime et baseline WebSocket
|
# Plan 0.3.4 — transport realtime et baseline WebSocket
|
||||||
|
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
Plan actif créé pendant `0.3.4-alpha.1` à partir de l'archive taggée `v0.3.3`.
|
Plan actif créé pendant `0.3.4-alpha.1` à partir de l'archive taggée `v0.3.3`.
|
||||||
|
|
||||||
La tranche `alpha.1` migre d'abord la gouvernance de version vers `alpha.N / beta.N / rc.N`, audite la baseline et ferme les décisions d'ownership avant toute implémentation réseau lourde.
|
La tranche `alpha.1` a été validée le 2026-09-21. La tranche active `alpha.2` matérialise maintenant le contrat transport-neutral sans dépendance runtime/backend ; le backend WebSocket reste réservé à `alpha.3`.
|
||||||
|
|
||||||
## Mission
|
## Mission
|
||||||
|
|
||||||
@@ -166,7 +166,9 @@ sender.close() -> async Result
|
|||||||
receiver.receive() -> async Result<Message | Closed>
|
receiver.receive() -> async Result<Message | Closed>
|
||||||
```
|
```
|
||||||
|
|
||||||
L'implémentation Rust exacte est décidée dans `alpha.2`, avec priorité à une API statiquement dispatchée et sans allocation de futures imposée uniquement pour obtenir un trait object.
|
`alpha.2` retient une API statiquement dispatchée fondée sur des associated types : `RealtimeConnection` produit un `Sender` et un `Receiver`, puis les opérations async exposent des futures associées GAT (`SendFuture`, `CloseFuture`, `ReceiveFuture`). Le contrat commun n'utilise ni `async-trait`, ni `Box<dyn Future>`, ni runtime concret.
|
||||||
|
|
||||||
|
Aucune borne `Send` n'est imposée aux futures par le contrat transport-neutral : un backend natif peut naturellement fournir des futures `Send`, tandis qu'un futur backend navigateur/WASM ne doit pas être rendu impossible par une contrainte de threading qui ne relève pas du transport abstrait.
|
||||||
|
|
||||||
Le contrat doit supporter lecture et écriture concurrentes après split. Il n'expose pas de runtime Tokio et ne crée aucun executor.
|
Le contrat doit supporter lecture et écriture concurrentes après split. Il n'expose pas de runtime Tokio et ne crée aucun executor.
|
||||||
|
|
||||||
@@ -391,6 +393,7 @@ Aucune dépendance réseau n'est ajoutée.
|
|||||||
- créer `game-realtime-transport-lib` ;
|
- créer `game-realtime-transport-lib` ;
|
||||||
- implémenter message, receive/close, erreurs et split ;
|
- implémenter message, receive/close, erreurs et split ;
|
||||||
- conserver l'API sans Tokio/Tungstenite ;
|
- conserver l'API sans Tokio/Tungstenite ;
|
||||||
|
- retenir des futures associées GAT afin de préserver le dispatch statique sans box ni contrainte `Send` imposée au contrat ;
|
||||||
- tests unitaires ciblés ;
|
- tests unitaires ciblés ;
|
||||||
- README/USAGE uniquement si une valeur durable est démontrée par `DOC-CRATE-*`.
|
- README/USAGE uniquement si une valeur durable est démontrée par `DOC-CRATE-*`.
|
||||||
|
|
||||||
|
|||||||
39
history/0.3.4/alpha.1.md
Normal file
39
history/0.3.4/alpha.1.md
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<!-- file: history/0.3.4/alpha.1.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Historique 0.3.4-alpha.1
|
||||||
|
|
||||||
|
## Statut
|
||||||
|
|
||||||
|
`0.3.4-alpha.1` a été validée par l'utilisateur le 2026-09-21 après application du delta sur son checkout issu de la stable `0.3.3`.
|
||||||
|
|
||||||
|
Aucun `alpha.1.fix.N` n'est requis. La suite peut ouvrir `0.3.4-alpha.2` et matérialiser l'API transport-neutral planifiée.
|
||||||
|
|
||||||
|
## Nettoyage et gates statiques
|
||||||
|
|
||||||
|
L'utilisateur a d'abord exécuté :
|
||||||
|
|
||||||
|
```text
|
||||||
|
cargo clean
|
||||||
|
Removed 57247 files, 28.5GiB total
|
||||||
|
```
|
||||||
|
|
||||||
|
Puis les audits et gates Rust ont tous terminé avec succès :
|
||||||
|
|
||||||
|
```text
|
||||||
|
General Rust rule audit: clean
|
||||||
|
Rust export completeness audit: 0 candidate(s)
|
||||||
|
games.sasedev workspace audit: clean
|
||||||
|
Markdown table audit: clean (5 table(s), 260 file(s))
|
||||||
|
Distribution layout audit: clean (49 required path(s), 8 forbidden path(s) absent)
|
||||||
|
cargo fmt --all: clean
|
||||||
|
cargo fmt --all -- --check: clean
|
||||||
|
cargo check --workspace: clean
|
||||||
|
cargo clippy --workspace --all-targets --all-features -- -D warnings: clean
|
||||||
|
```
|
||||||
|
|
||||||
|
La reconstruction complète après `cargo clean` confirme notamment le workspace à `0.3.4-alpha.1` sur les moteurs, jeux, adapters Desktop/Android/WASM/Tauri et bibliothèques communes.
|
||||||
|
|
||||||
|
## Conséquence
|
||||||
|
|
||||||
|
La migration de gouvernance et le cadrage de `0.3.4` sont acceptés. `alpha.2` peut ajouter uniquement le contrat commun realtime, sans Tokio, Tungstenite ni backend WebSocket ; ces dépendances restent réservées à la tranche suivante.
|
||||||
Reference in New Issue
Block a user