arch: split ConfigRepository, extract polling, consolidate conversions, decouple protocol

- Value↔JSON: From impls on domain Value behind `json` feature, delete 4 duplicate converters
- ConfigRepository split into ConfigRepository (12), UserRepository (3), WidgetStateCache (2)
- polling orchestration moved from bootstrap to application::polling_service
- WidgetRenderer in client-domain owns scroll/cache, both clients use it
- network loop consolidated into client-application::run_connection_loop
- protocol crate drops domain dep, Wire↔Domain conversions move to adapters
This commit is contained in:
2026-06-19 18:12:50 +02:00
parent 1c854d127f
commit 7001b5e911
46 changed files with 1063 additions and 951 deletions

View File

@@ -1,12 +1,13 @@
use client_application::ClientApp;
use client_domain::NetworkPort;
use client_domain::{BoundingBox, DisplayPort, FontMetrics, RenderEngine, ThemeConfig};
use client_application::{ClientApp, conversions, run_connection_loop};
use client_domain::{
BoundingBox, DisplayPort, FontMetrics, RenderEngine, RepaintRequest, ThemeConfig,
WidgetRenderer,
};
use display_terminal::TerminalDisplay;
use domain::{DisplayHint, WidgetError};
use protocol::decode_server_message;
use protocol::ServerMessage;
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
use std::time::{Duration, Instant};
use tcp_client::StdTcpClient;
fn main() {
@@ -18,52 +19,33 @@ fn main() {
large: (10, 20),
};
let mut engine = RenderEngine::new(metrics, ThemeConfig::default());
let mut renderer = WidgetRenderer::new();
println!("=== K-Frame Desktop Client ===");
println!("Screen: {}x{}", screen.width, screen.height);
let (tx, rx) = mpsc::channel();
let (tx, rx) = mpsc::channel::<ServerMessage>();
thread::spawn(move || {
let server_addr = "127.0.0.1:2699";
let mut net = StdTcpClient::new();
loop {
if !net.is_connected() {
println!("[NET] Connecting to {server_addr}...");
match net.connect(server_addr) {
Ok(()) => println!("[NET] Connected!"),
Err(e) => {
println!("[NET] Connection failed: {e}, retrying in 2s...");
thread::sleep(Duration::from_secs(2));
continue;
}
}
}
match net.receive() {
Ok(Some(payload)) => match decode_server_message(&payload) {
Ok(msg) => {
let _ = tx.send(msg);
}
Err(e) => println!("[NET] Decode error: {e}"),
},
Ok(None) => {
thread::sleep(Duration::from_millis(50));
}
Err(e) => {
println!("[NET] Receive error: {e}, reconnecting...");
let _ = net.disconnect();
thread::sleep(Duration::from_secs(2));
}
}
}
let tx_clone = tx.clone();
run_connection_loop(
&mut net,
"127.0.0.1:2699",
Duration::from_millis(50),
Duration::from_secs(2),
move |msg| {
let _ = tx_clone.send(msg);
},
|_connected| {},
);
});
println!("[RENDER] Render loop started");
let mut last_tick = Instant::now();
loop {
match rx.recv_timeout(Duration::from_millis(100)) {
match rx.recv_timeout(Duration::from_millis(50)) {
Ok(msg) => {
let repaints = app.handle_message(msg);
@@ -73,23 +55,36 @@ fn main() {
if !repaints.is_empty() {
println!("\n--- Repaint ({} widgets) ---", repaints.len());
let requests: Vec<RepaintRequest> = repaints
.iter()
.map(|cmd| RepaintRequest {
widget_id: cmd.widget_id,
bounds: cmd.bounds,
display_hint: conversions::wire_to_display_hint(
cmd.display_hint.clone(),
),
data: cmd
.state
.data
.iter()
.map(|kv| {
(kv.key.clone(), conversions::wire_to_value(kv.value.clone()))
})
.collect(),
error: cmd
.state
.error
.as_ref()
.map(|e| conversions::wire_to_widget_error(e.clone())),
})
.collect();
let bg = engine.theme().background;
for cmd in &repaints {
display.fill_rect(cmd.bounds, bg).unwrap();
let hint: DisplayHint = cmd.display_hint.clone().into();
let data: Vec<(String, domain::Value)> = cmd
.state
.data
.iter()
.map(|kv| (kv.key.clone(), kv.value.clone().into()))
.collect();
let error: Option<WidgetError> =
cmd.state.error.as_ref().map(|e| e.clone().into());
let draw_cmds =
engine.render_widget(&hint, &data, cmd.bounds, 0, error.as_ref());
for dc in &draw_cmds {
let updates = renderer.apply_repaints(&engine, requests);
for update in &updates {
display.fill_rect(update.bounds, bg).unwrap();
for dc in &update.commands {
display
.draw_text_span(&dc.text, dc.x, dc.y, dc.color, dc.font)
.unwrap();
@@ -101,5 +96,23 @@ fn main() {
Err(mpsc::RecvTimeoutError::Timeout) => {}
Err(mpsc::RecvTimeoutError::Disconnected) => break,
}
let now = Instant::now();
let elapsed = now.duration_since(last_tick);
last_tick = now;
let scroll_updates = renderer.tick_scroll(&engine, elapsed);
if !scroll_updates.is_empty() {
let bg = engine.theme().background;
for update in &scroll_updates {
display.fill_rect(update.bounds, bg).unwrap();
for dc in &update.commands {
display
.draw_text_span(&dc.text, dc.x, dc.y, dc.color, dc.font)
.unwrap();
}
}
display.flush().unwrap();
}
}
}