Compare commits
45 Commits
d8e8b1701d
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| efb21705c3 | |||
| 34f57ce0b5 | |||
| 4aa7219d49 | |||
| 9511b4443b | |||
| 089ed08c16 | |||
| f9376eb6d4 | |||
| a1928edb66 | |||
| 09be172e36 | |||
| d8a96bc956 | |||
| 02696b1f75 | |||
| 1904560244 | |||
| 24bb65bf7b | |||
| 4aa22de61b | |||
| 7557507f27 | |||
| 029d62cb53 | |||
| e1d0a423de | |||
| c1040f9789 | |||
| cd348dd978 | |||
| f7e9346187 | |||
| c31c3d90eb | |||
| 80e328e1aa | |||
| 7e15bfa494 | |||
| 3b38df6f39 | |||
| 4faa1b601a | |||
| 2928482322 | |||
| ac6ee0b119 | |||
| 7644aa83bb | |||
| b53a01cc46 | |||
| 90f9c13dd7 | |||
| 1008f97a52 | |||
| c8e70c0141 | |||
| c2891654a3 | |||
| bbe8ea3a85 | |||
| 528a7503fc | |||
| c0de059241 | |||
| d93ced057a | |||
| 775d8c4891 | |||
| 8eda92f5f5 | |||
| 6eac4f4576 | |||
| ce07c80782 | |||
| 6405c1bb3b | |||
| bdcdb01524 | |||
| d9f1620e8d | |||
| 0c707c62d1 | |||
| 416d67103d |
911
Cargo.lock
generated
911
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
13
Cargo.toml
13
Cargo.toml
@@ -1,3 +1,5 @@
|
|||||||
|
cargo-features = ["codegen-backend"]
|
||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "openworm"
|
name = "openworm"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -18,6 +20,11 @@ winit = "0.30.12"
|
|||||||
bincode = "2.0.1"
|
bincode = "2.0.1"
|
||||||
zstd = "0.13.3"
|
zstd = "0.13.3"
|
||||||
ron = "0.12.0"
|
ron = "0.12.0"
|
||||||
|
sled = "0.34.7"
|
||||||
|
clap = { version = "4.5.53", features = ["derive"] }
|
||||||
|
scrypt = "0.11.0"
|
||||||
|
ed25519-dalek = { version = "3.0.0-pre.2", features = ["rand_core"] }
|
||||||
|
rand = { version = "0.10.0-rc.5", features = ["chacha"] }
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "openworm-client"
|
name = "openworm-client"
|
||||||
@@ -26,3 +33,9 @@ path = "src/bin/client/main.rs"
|
|||||||
[[bin]]
|
[[bin]]
|
||||||
name = "openworm-server"
|
name = "openworm-server"
|
||||||
path = "src/bin/server/main.rs"
|
path = "src/bin/server/main.rs"
|
||||||
|
|
||||||
|
[profile.dev]
|
||||||
|
codegen-backend = "cranelift"
|
||||||
|
|
||||||
|
[profile.dev.package."*"]
|
||||||
|
opt-level = 1
|
||||||
|
|||||||
2
iris
2
iris
Submodule iris updated: db248de8f4...7f4846a2d3
22
src/bin/client/account.rs
Normal file
22
src/bin/client/account.rs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
use ed25519_dalek::SigningKey;
|
||||||
|
use rand::{
|
||||||
|
SeedableRng,
|
||||||
|
rngs::{OsRng, StdRng},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct Account {
|
||||||
|
device_key: SigningKey,
|
||||||
|
account_key: SigningKey,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Account {
|
||||||
|
pub fn new() -> Account {
|
||||||
|
let mut csprng = StdRng::try_from_rng(&mut OsRng).unwrap();
|
||||||
|
let device_key = SigningKey::generate(&mut csprng);
|
||||||
|
let account_key = SigningKey::generate(&mut csprng);
|
||||||
|
Account {
|
||||||
|
device_key,
|
||||||
|
account_key,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use winit::{
|
|
||||||
application::ApplicationHandler,
|
|
||||||
event::WindowEvent,
|
|
||||||
event_loop::{ActiveEventLoop, EventLoop, EventLoopProxy},
|
|
||||||
window::{Window, WindowId},
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::ClientEvent;
|
|
||||||
|
|
||||||
use super::Client;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct AppHandle {
|
|
||||||
pub proxy: EventLoopProxy<ClientEvent>,
|
|
||||||
pub window: Arc<Window>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct App {
|
|
||||||
client: Option<Client>,
|
|
||||||
proxy: EventLoopProxy<ClientEvent>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl App {
|
|
||||||
pub fn run() {
|
|
||||||
let event_loop = EventLoop::with_user_event().build().unwrap();
|
|
||||||
let proxy = event_loop.create_proxy();
|
|
||||||
event_loop
|
|
||||||
.run_app(&mut App {
|
|
||||||
client: Default::default(),
|
|
||||||
proxy,
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ApplicationHandler<ClientEvent> for App {
|
|
||||||
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
|
|
||||||
if self.client.is_none() {
|
|
||||||
let client = Client::new(event_loop, self.proxy.clone());
|
|
||||||
self.client = Some(client);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) {
|
|
||||||
let client = self.client.as_mut().unwrap();
|
|
||||||
client.window_event(event, event_loop);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn user_event(&mut self, event_loop: &ActiveEventLoop, event: ClientEvent) {
|
|
||||||
let client = self.client.as_mut().unwrap();
|
|
||||||
client.event(event, event_loop);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn exiting(&mut self, _: &ActiveEventLoop) {
|
|
||||||
let client = self.client.as_mut().unwrap();
|
|
||||||
client.exit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AppHandle {
|
|
||||||
pub fn send(&self, event: ClientEvent) {
|
|
||||||
self.proxy.send_event(event).unwrap_or_else(|_| panic!());
|
|
||||||
self.window.request_redraw();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
BIN
src/bin/client/assets/fuit.jpg
Normal file
BIN
src/bin/client/assets/fuit.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
28
src/bin/client/debug.rs
Normal file
28
src/bin/client/debug.rs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
use crate::Client;
|
||||||
|
use iris::prelude::*;
|
||||||
|
|
||||||
|
pub fn debug(_client: &mut Client, ui: &mut Ui, _state: &UiState) {
|
||||||
|
ui.debug_layers();
|
||||||
|
// let mut file = std::fs::OpenOptions::new()
|
||||||
|
// .write(true)
|
||||||
|
// .create(true)
|
||||||
|
// .truncate(true)
|
||||||
|
// .open("./old_msgs")
|
||||||
|
// .unwrap();
|
||||||
|
// bincode::encode_into_std_write(
|
||||||
|
// self.msgs.clone(),
|
||||||
|
// &mut file,
|
||||||
|
// openworm::net::BINCODE_CONFIG,
|
||||||
|
// )
|
||||||
|
// .unwrap();
|
||||||
|
// let mut file = std::fs::OpenOptions::new()
|
||||||
|
// .read(true)
|
||||||
|
// .open("./old_msgs")
|
||||||
|
// .unwrap();
|
||||||
|
// let msgs: Vec<NetMsg> =
|
||||||
|
// bincode::decode_from_std_read(&mut file, openworm::net::BINCODE_CONFIG).unwrap();
|
||||||
|
// for msg in msgs {
|
||||||
|
// println!("{msg:?}");
|
||||||
|
// }
|
||||||
|
// client.ui.debug_layers();
|
||||||
|
}
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
use iris::{
|
|
||||||
core::{CursorState, Modifiers},
|
|
||||||
layout::Vec2,
|
|
||||||
};
|
|
||||||
use winit::{
|
|
||||||
event::{MouseButton, MouseScrollDelta, WindowEvent},
|
|
||||||
keyboard::{Key, NamedKey},
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::Client;
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct Input {
|
|
||||||
cursor: CursorState,
|
|
||||||
pub modifiers: Modifiers,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Input {
|
|
||||||
pub fn event(&mut self, event: &WindowEvent) -> bool {
|
|
||||||
match event {
|
|
||||||
WindowEvent::CursorMoved { position, .. } => {
|
|
||||||
self.cursor.pos = Vec2::new(position.x as f32, position.y as f32);
|
|
||||||
self.cursor.exists = true;
|
|
||||||
}
|
|
||||||
WindowEvent::MouseInput { state, button, .. } => {
|
|
||||||
let buttons = &mut self.cursor.buttons;
|
|
||||||
let pressed = state.is_pressed();
|
|
||||||
match button {
|
|
||||||
MouseButton::Left => buttons.left.update(pressed),
|
|
||||||
MouseButton::Right => buttons.right.update(pressed),
|
|
||||||
MouseButton::Middle => buttons.middle.update(pressed),
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
WindowEvent::MouseWheel { delta, .. } => {
|
|
||||||
let mut delta = match *delta {
|
|
||||||
MouseScrollDelta::LineDelta(x, y) => Vec2::new(x, y),
|
|
||||||
MouseScrollDelta::PixelDelta(pos) => Vec2::new(pos.x as f32, pos.y as f32),
|
|
||||||
};
|
|
||||||
if delta.x == 0.0 && self.modifiers.shift {
|
|
||||||
delta.x = delta.y;
|
|
||||||
delta.y = 0.0;
|
|
||||||
}
|
|
||||||
self.cursor.scroll_delta = delta;
|
|
||||||
}
|
|
||||||
WindowEvent::CursorLeft { .. } => {
|
|
||||||
self.cursor.exists = false;
|
|
||||||
self.modifiers.clear();
|
|
||||||
}
|
|
||||||
WindowEvent::KeyboardInput { event, .. } => {
|
|
||||||
if let Key::Named(named) = event.logical_key {
|
|
||||||
let pressed = event.state.is_pressed();
|
|
||||||
match named {
|
|
||||||
NamedKey::Control => {
|
|
||||||
self.modifiers.control = pressed;
|
|
||||||
}
|
|
||||||
NamedKey::Shift => {
|
|
||||||
self.modifiers.shift = pressed;
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => return false,
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn end_frame(&mut self) {
|
|
||||||
self.cursor.end_frame();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Client {
|
|
||||||
pub fn window_size(&self) -> Vec2 {
|
|
||||||
let size = self.renderer.window().inner_size();
|
|
||||||
(size.width, size.height).into()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn cursor_state(&self) -> &CursorState {
|
|
||||||
&self.input.cursor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,203 +1,157 @@
|
|||||||
#![windows_subsystem = "windows"]
|
#![windows_subsystem = "windows"]
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app::App,
|
net::{NetHandle, NetSender},
|
||||||
net::NetSender,
|
|
||||||
rsc::{CLIENT_DATA, ClientData},
|
rsc::{CLIENT_DATA, ClientData},
|
||||||
|
state::{ClientState, LoggedIn, Login},
|
||||||
ui::*,
|
ui::*,
|
||||||
};
|
};
|
||||||
pub use app::AppHandle;
|
|
||||||
use arboard::Clipboard;
|
|
||||||
use input::Input;
|
|
||||||
use iris::prelude::*;
|
use iris::prelude::*;
|
||||||
use openworm::{
|
use openworm::{
|
||||||
net::{ClientMsg, ServerMsg, install_crypto_provider},
|
net::{ClientMsg, ServerMsg, install_crypto_provider},
|
||||||
rsc::DataDir,
|
rsc::DataDir,
|
||||||
};
|
};
|
||||||
use render::Renderer;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use winit::{
|
use winit::{
|
||||||
event::{Ime, WindowEvent},
|
event::{ElementState, MouseButton, WindowEvent},
|
||||||
event_loop::{ActiveEventLoop, EventLoopProxy},
|
window::WindowAttributes,
|
||||||
window::Window,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
mod app;
|
mod account;
|
||||||
mod input;
|
mod debug;
|
||||||
mod net;
|
mod net;
|
||||||
mod render;
|
|
||||||
mod rsc;
|
mod rsc;
|
||||||
|
mod state;
|
||||||
mod ui;
|
mod ui;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
install_crypto_provider();
|
install_crypto_provider();
|
||||||
App::run();
|
DefaultApp::<Client>::run();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Client {
|
||||||
|
dir: DataDir,
|
||||||
|
data: ClientData,
|
||||||
|
state: ClientState,
|
||||||
|
main_ui: WidgetRef<WidgetPtr>,
|
||||||
|
notif: WidgetRef<WidgetPtr>,
|
||||||
|
proxy: Proxy<ClientEvent>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum ClientEvent {
|
pub enum ClientEvent {
|
||||||
Connect { send: NetSender, username: String },
|
Connect { send: NetSender },
|
||||||
ServerMsg(ServerMsg),
|
ServerMsg(ServerMsg),
|
||||||
Err(String),
|
Err(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Client {
|
impl DefaultAppState for Client {
|
||||||
renderer: Renderer,
|
type Event = ClientEvent;
|
||||||
input: Input,
|
|
||||||
ui: Ui,
|
|
||||||
focus: Option<WidgetId<TextEdit>>,
|
|
||||||
channel: Option<WidgetId<Span>>,
|
|
||||||
username: String,
|
|
||||||
clipboard: Clipboard,
|
|
||||||
dir: DataDir,
|
|
||||||
data: ClientData,
|
|
||||||
handle: AppHandle,
|
|
||||||
error: Option<WidgetId<WidgetPtr>>,
|
|
||||||
ime: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Client {
|
fn window_attrs() -> WindowAttributes {
|
||||||
pub fn new(event_loop: &ActiveEventLoop, proxy: EventLoopProxy<ClientEvent>) -> Self {
|
WindowAttributes::default().with_title("OPENWORM")
|
||||||
let window = Arc::new(
|
}
|
||||||
event_loop
|
|
||||||
.create_window(Window::default_attributes().with_title("OPENWORM"))
|
fn new(ui: &mut Ui, _state: &UiState, proxy: Proxy<Self::Event>) -> Self {
|
||||||
.unwrap(),
|
|
||||||
);
|
|
||||||
let renderer = Renderer::new(window.clone());
|
|
||||||
let dir = DataDir::default();
|
let dir = DataDir::default();
|
||||||
let handle = AppHandle { proxy, window };
|
let notif = WidgetPtr::default().add(ui);
|
||||||
|
let main_ui = WidgetPtr::default().add(ui);
|
||||||
|
let bg = (
|
||||||
|
image(include_bytes!("./assets/fuit.jpg")),
|
||||||
|
rect(Color::BLACK.alpha((0.8 * 255.0) as u8)),
|
||||||
|
)
|
||||||
|
.stack();
|
||||||
|
|
||||||
|
(
|
||||||
|
bg,
|
||||||
|
main_ui.clone(),
|
||||||
|
notif.clone().pad(Padding::top(10)).align(Align::TOP_CENTER),
|
||||||
|
)
|
||||||
|
.stack()
|
||||||
|
.set_root(ui);
|
||||||
|
|
||||||
let mut s = Self {
|
let mut s = Self {
|
||||||
handle,
|
|
||||||
renderer,
|
|
||||||
input: Input::default(),
|
|
||||||
ui: Ui::new(),
|
|
||||||
data: dir.load(CLIENT_DATA),
|
data: dir.load(CLIENT_DATA),
|
||||||
|
state: Default::default(),
|
||||||
dir,
|
dir,
|
||||||
channel: None,
|
main_ui: main_ui.clone(),
|
||||||
focus: None,
|
notif,
|
||||||
username: "<unknown>".to_string(),
|
proxy,
|
||||||
clipboard: Clipboard::new().unwrap(),
|
|
||||||
error: None,
|
|
||||||
ime: 0,
|
|
||||||
};
|
};
|
||||||
ui::init(&mut s);
|
start_ui(&mut s, ui).set_ptr(&s.main_ui, ui);
|
||||||
s
|
s
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn event(&mut self, event: ClientEvent, _: &ActiveEventLoop) {
|
fn event(&mut self, event: ClientEvent, ui: &mut Ui, _state: &UiState) {
|
||||||
match event {
|
match event {
|
||||||
ClientEvent::Connect { send, username } => {
|
ClientEvent::Connect { send } => {
|
||||||
self.username = username;
|
let ClientState::Connect(connect) = self.state.take() else {
|
||||||
send.send(ClientMsg::RequestMsgs);
|
panic!("invalid state");
|
||||||
main_view(self, send).set_root(&mut self.ui);
|
};
|
||||||
|
let th = connect.handle.unwrap();
|
||||||
|
self.state = ClientState::Login(Login {
|
||||||
|
handle: NetHandle {
|
||||||
|
send: send.clone(),
|
||||||
|
thread: th,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// login_screen(self, ui).set_ptr(&self.main_ui, ui);
|
||||||
}
|
}
|
||||||
ClientEvent::ServerMsg(msg) => match msg {
|
ClientEvent::ServerMsg(msg) => match msg {
|
||||||
ServerMsg::SendMsg(msg) => {
|
ServerMsg::SendMsg(msg) => {
|
||||||
if let Some(msg_area) = &self.channel {
|
if let ClientState::LoggedIn(state) = &mut self.state
|
||||||
let msg = msg_widget(msg).add(&mut self.ui);
|
&& let Some(msg_area) = &state.channel
|
||||||
self.ui[msg_area].children.push(msg.any());
|
{
|
||||||
|
let msg = msg_widget(&msg.user, &msg.content).add(ui);
|
||||||
|
msg_area.get_mut().children.push(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ServerMsg::LoadMsgs(msgs) => {
|
ServerMsg::LoadMsgs(msgs) => {
|
||||||
if let Some(msg_area) = &self.channel {
|
if let ClientState::LoggedIn(state) = &mut self.state
|
||||||
|
&& let Some(msg_area) = &state.channel
|
||||||
|
{
|
||||||
for msg in msgs {
|
for msg in msgs {
|
||||||
let msg = msg_widget(msg).add(&mut self.ui);
|
state.msgs.push(msg.clone());
|
||||||
self.ui[msg_area].children.push(msg.any());
|
let msg = msg_widget(&msg.user, &msg.content).add(ui);
|
||||||
|
msg_area.get_mut().children.push(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ServerMsg::Login { username } => {
|
||||||
|
let ClientState::Login(state) = self.state.take() else {
|
||||||
|
panic!("invalid state");
|
||||||
|
};
|
||||||
|
state.handle.send(ClientMsg::RequestMsgs);
|
||||||
|
self.state = ClientState::LoggedIn(LoggedIn {
|
||||||
|
network: state.handle,
|
||||||
|
channel: None,
|
||||||
|
msgs: Vec::new(),
|
||||||
|
username,
|
||||||
|
});
|
||||||
|
main_view(self, ui).set_ptr(&self.main_ui, ui);
|
||||||
|
}
|
||||||
|
ServerMsg::Error(error) => {
|
||||||
|
let msg = format!("{error:?}");
|
||||||
|
self.notif.get_mut().inner = Some(werror(ui, &msg));
|
||||||
|
}
|
||||||
},
|
},
|
||||||
ClientEvent::Err(msg) => {
|
ClientEvent::Err(msg) => {
|
||||||
if let Some(err) = &self.error {
|
self.notif.get_mut().inner = Some(werror(ui, &msg));
|
||||||
self.ui[err].inner = Some(ui::error(&mut self.ui, &msg));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn window_event(&mut self, event: WindowEvent, event_loop: &ActiveEventLoop) {
|
fn exit(&mut self, _ui: &mut Ui, _state: &UiState) {
|
||||||
let input_changed = self.input.event(&event);
|
self.state.exit();
|
||||||
let cursor_state = self.cursor_state().clone();
|
|
||||||
if let Some(focus) = &self.focus
|
|
||||||
&& cursor_state.buttons.left.is_start()
|
|
||||||
{
|
|
||||||
self.ui.text(focus).deselect();
|
|
||||||
self.focus = None;
|
|
||||||
}
|
|
||||||
if input_changed {
|
|
||||||
let window_size = self.window_size();
|
|
||||||
self.run_sensors(&cursor_state, window_size);
|
|
||||||
self.ui.run_sensors(&cursor_state, window_size);
|
|
||||||
}
|
|
||||||
match event {
|
|
||||||
WindowEvent::CloseRequested => event_loop.exit(),
|
|
||||||
WindowEvent::RedrawRequested => {
|
|
||||||
self.ui.update();
|
|
||||||
self.renderer.update(&mut self.ui);
|
|
||||||
self.renderer.draw();
|
|
||||||
}
|
|
||||||
WindowEvent::Resized(size) => {
|
|
||||||
self.ui.resize((size.width, size.height));
|
|
||||||
self.renderer.resize(&size)
|
|
||||||
}
|
|
||||||
WindowEvent::KeyboardInput { event, .. } => {
|
|
||||||
if let Some(sel) = &self.focus
|
|
||||||
&& event.state.is_pressed()
|
|
||||||
{
|
|
||||||
let sel = &sel.clone();
|
|
||||||
let mut text = self.ui.text(sel);
|
|
||||||
match text.apply_event(&event, &self.input.modifiers) {
|
|
||||||
TextInputResult::Unfocus => {
|
|
||||||
self.focus = None;
|
|
||||||
self.handle.window.set_ime_allowed(false);
|
|
||||||
}
|
|
||||||
TextInputResult::Submit => {
|
|
||||||
self.run_event(sel, Submit, ());
|
|
||||||
}
|
|
||||||
TextInputResult::Paste => {
|
|
||||||
if let Ok(t) = self.clipboard.get_text() {
|
|
||||||
text.insert(&t);
|
|
||||||
}
|
|
||||||
self.run_event(sel, Edited, ());
|
|
||||||
}
|
|
||||||
TextInputResult::Used => {
|
|
||||||
self.run_event(sel, Edited, ());
|
|
||||||
}
|
|
||||||
TextInputResult::Unused => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
WindowEvent::Ime(ime) => {
|
|
||||||
if let Some(sel) = &self.focus {
|
|
||||||
let mut text = self.ui.text(sel);
|
|
||||||
match ime {
|
|
||||||
Ime::Enabled | Ime::Disabled => (),
|
|
||||||
Ime::Preedit(content, _pos) => {
|
|
||||||
// TODO: highlight once that's real
|
|
||||||
text.replace(self.ime, &content);
|
|
||||||
self.ime = content.chars().count();
|
|
||||||
}
|
|
||||||
Ime::Commit(content) => {
|
|
||||||
text.insert(&content);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
if self.ui.needs_redraw() {
|
|
||||||
self.renderer.window().request_redraw();
|
|
||||||
}
|
|
||||||
self.input.end_frame();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn exit(&mut self) {
|
|
||||||
self.dir.save(CLIENT_DATA, &self.data);
|
self.dir.save(CLIENT_DATA, &self.data);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl UiCtx for Client {
|
fn window_event(&mut self, event: WindowEvent, ui: &mut Ui, state: &UiState) {
|
||||||
fn ui(&mut self) -> &mut Ui {
|
if let WindowEvent::MouseInput {
|
||||||
&mut self.ui
|
state: ElementState::Pressed,
|
||||||
|
button: MouseButton::Middle,
|
||||||
|
..
|
||||||
|
} = event
|
||||||
|
{
|
||||||
|
debug::debug(self, ui, state);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::{AppHandle, ClientEvent};
|
use crate::ClientEvent;
|
||||||
use openworm::net::{
|
use openworm::net::{
|
||||||
ClientMsg, RecvHandler, SERVER_NAME, ServerMsg, SkipServerVerification, recv_uni, send_uni,
|
ClientMsg, RecvHandler, SERVER_NAME, ServerMsg, SkipServerVerification, recv_uni, send_uni,
|
||||||
};
|
};
|
||||||
@@ -9,36 +9,77 @@ use quinn::{
|
|||||||
use std::{
|
use std::{
|
||||||
net::{Ipv6Addr, SocketAddr, SocketAddrV6, ToSocketAddrs},
|
net::{Ipv6Addr, SocketAddr, SocketAddrV6, ToSocketAddrs},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
|
thread::JoinHandle,
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
use tokio::sync::mpsc::UnboundedSender;
|
use tokio::sync::mpsc::UnboundedSender;
|
||||||
|
use winit::{event_loop::EventLoopProxy, window::Window};
|
||||||
|
|
||||||
pub const CLIENT_SOCKET: SocketAddr =
|
pub const CLIENT_SOCKET: SocketAddr =
|
||||||
SocketAddr::V6(SocketAddrV6::new(Ipv6Addr::UNSPECIFIED, 0, 0, 0));
|
SocketAddr::V6(SocketAddrV6::new(Ipv6Addr::UNSPECIFIED, 0, 0, 0));
|
||||||
|
|
||||||
pub struct ConnectInfo {
|
pub struct ConnectInfo {
|
||||||
pub ip: String,
|
pub ip: String,
|
||||||
pub username: String,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn connect(handle: AppHandle, info: ConnectInfo) {
|
pub struct NetHandle {
|
||||||
|
pub send: NetSender,
|
||||||
|
pub thread: JoinHandle<()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct AppHandle {
|
||||||
|
pub proxy: EventLoopProxy<ClientEvent>,
|
||||||
|
pub window: Arc<Window>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppHandle {
|
||||||
|
pub fn send(&self, event: ClientEvent) {
|
||||||
|
self.proxy.send_event(event).unwrap_or_else(|_| panic!());
|
||||||
|
self.window.request_redraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn connect(handle: AppHandle, info: ConnectInfo) -> JoinHandle<()> {
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
if let Err(msg) = connect_the(handle.clone(), info) {
|
if let Err(msg) = connect_the(handle.clone(), info) {
|
||||||
handle.send(ClientEvent::Err(msg));
|
handle.send(ClientEvent::Err(msg));
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
type NetResult<T> = Result<T, String>;
|
type NetResult<T> = Result<T, String>;
|
||||||
|
|
||||||
type MsgPayload = ClientMsg;
|
#[derive(Clone)]
|
||||||
pub struct NetSender {
|
pub struct NetSender {
|
||||||
send: UnboundedSender<MsgPayload>,
|
send: UnboundedSender<NetCtrlMsg>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum NetCtrlMsg {
|
||||||
|
Send(ClientMsg),
|
||||||
|
Exit,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ClientMsg> for NetCtrlMsg {
|
||||||
|
fn from(value: ClientMsg) -> Self {
|
||||||
|
Self::Send(value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NetSender {
|
impl NetSender {
|
||||||
pub fn send(&self, msg: ClientMsg) {
|
pub fn send(&self, msg: impl Into<NetCtrlMsg>) {
|
||||||
let _ = self.send.send(msg);
|
let _ = self.send.send(msg.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NetHandle {
|
||||||
|
pub fn send(&self, msg: impl Into<NetCtrlMsg>) {
|
||||||
|
self.send.send(msg.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn exit(self) {
|
||||||
|
self.send(NetCtrlMsg::Exit);
|
||||||
|
let _ = self.thread.join();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,7 +109,7 @@ impl NetSender {
|
|||||||
// .map_err(|e| format!("failed to connect: {}", e))
|
// .map_err(|e| format!("failed to connect: {}", e))
|
||||||
// }
|
// }
|
||||||
|
|
||||||
async fn connection_no_cert(addr: SocketAddr) -> NetResult<Connection> {
|
async fn connection_no_cert(addr: SocketAddr) -> NetResult<(Endpoint, Connection)> {
|
||||||
let mut endpoint = Endpoint::client(CLIENT_SOCKET).map_err(|e| e.to_string())?;
|
let mut endpoint = Endpoint::client(CLIENT_SOCKET).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
let quic = QuicClientConfig::try_from(
|
let quic = QuicClientConfig::try_from(
|
||||||
@@ -91,16 +132,17 @@ async fn connection_no_cert(addr: SocketAddr) -> NetResult<Connection> {
|
|||||||
endpoint.set_default_client_config(config);
|
endpoint.set_default_client_config(config);
|
||||||
|
|
||||||
// connect to server
|
// connect to server
|
||||||
endpoint
|
let con = endpoint
|
||||||
.connect(addr, SERVER_NAME)
|
.connect(addr, SERVER_NAME)
|
||||||
.map_err(|e| e.to_string())?
|
.map_err(|e| e.to_string())?
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())
|
.map_err(|e| e.to_string())?;
|
||||||
|
Ok((endpoint, con))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn connect_the(handle: AppHandle, info: ConnectInfo) -> NetResult<()> {
|
async fn connect_the(handle: AppHandle, info: ConnectInfo) -> NetResult<()> {
|
||||||
let (send, mut ui_recv) = tokio::sync::mpsc::unbounded_channel::<MsgPayload>();
|
let (send, mut ui_recv) = tokio::sync::mpsc::unbounded_channel::<NetCtrlMsg>();
|
||||||
|
|
||||||
let addr = info
|
let addr = info
|
||||||
.ip
|
.ip
|
||||||
@@ -108,11 +150,10 @@ async fn connect_the(handle: AppHandle, info: ConnectInfo) -> NetResult<()> {
|
|||||||
.map_err(|e| e.to_string())?
|
.map_err(|e| e.to_string())?
|
||||||
.next()
|
.next()
|
||||||
.ok_or("no addresses found".to_string())?;
|
.ok_or("no addresses found".to_string())?;
|
||||||
let conn = connection_no_cert(addr).await?;
|
let (endpoint, conn) = connection_no_cert(addr).await?;
|
||||||
let conn_ = conn.clone();
|
let conn_ = conn.clone();
|
||||||
|
|
||||||
handle.send(ClientEvent::Connect {
|
handle.send(ClientEvent::Connect {
|
||||||
username: info.username,
|
|
||||||
send: NetSender { send },
|
send: NetSender { send },
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -120,11 +161,20 @@ async fn connect_the(handle: AppHandle, info: ConnectInfo) -> NetResult<()> {
|
|||||||
tokio::spawn(recv_uni(conn_, recv.into()));
|
tokio::spawn(recv_uni(conn_, recv.into()));
|
||||||
|
|
||||||
while let Some(msg) = ui_recv.recv().await {
|
while let Some(msg) = ui_recv.recv().await {
|
||||||
|
match msg {
|
||||||
|
NetCtrlMsg::Send(msg) => {
|
||||||
if send_uni(&conn, msg).await.is_err() {
|
if send_uni(&conn, msg).await.is_err() {
|
||||||
println!("disconnected from server");
|
println!("disconnected from server");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
NetCtrlMsg::Exit => {
|
||||||
|
conn.close(0u32.into(), &[]);
|
||||||
|
endpoint.wait_idle().await;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,151 +0,0 @@
|
|||||||
use iris::{
|
|
||||||
layout::Ui,
|
|
||||||
render::{UiLimits, UiRenderer},
|
|
||||||
};
|
|
||||||
use pollster::FutureExt;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use wgpu::{util::StagingBelt, *};
|
|
||||||
use winit::{dpi::PhysicalSize, window::Window};
|
|
||||||
|
|
||||||
pub const CLEAR_COLOR: Color = Color::BLACK;
|
|
||||||
|
|
||||||
pub struct Renderer {
|
|
||||||
window: Arc<Window>,
|
|
||||||
surface: Surface<'static>,
|
|
||||||
device: Device,
|
|
||||||
queue: Queue,
|
|
||||||
config: SurfaceConfiguration,
|
|
||||||
encoder: CommandEncoder,
|
|
||||||
staging_belt: StagingBelt,
|
|
||||||
pub ui: UiRenderer,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Renderer {
|
|
||||||
pub fn update(&mut self, updates: &mut Ui) {
|
|
||||||
self.ui.update(&self.device, &self.queue, updates);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn draw(&mut self) {
|
|
||||||
let output = self.surface.get_current_texture().unwrap();
|
|
||||||
let view = output
|
|
||||||
.texture
|
|
||||||
.create_view(&TextureViewDescriptor::default());
|
|
||||||
|
|
||||||
let mut encoder = std::mem::replace(&mut self.encoder, Self::create_encoder(&self.device));
|
|
||||||
{
|
|
||||||
let render_pass = &mut encoder.begin_render_pass(&RenderPassDescriptor {
|
|
||||||
color_attachments: &[Some(RenderPassColorAttachment {
|
|
||||||
view: &view,
|
|
||||||
resolve_target: None,
|
|
||||||
ops: Operations {
|
|
||||||
load: LoadOp::Clear(CLEAR_COLOR),
|
|
||||||
store: StoreOp::Store,
|
|
||||||
},
|
|
||||||
depth_slice: None,
|
|
||||||
})],
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
self.ui.draw(render_pass);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.queue.submit(std::iter::once(encoder.finish()));
|
|
||||||
self.staging_belt.finish();
|
|
||||||
output.present();
|
|
||||||
self.staging_belt.recall();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn resize(&mut self, size: &PhysicalSize<u32>) {
|
|
||||||
self.config.width = size.width;
|
|
||||||
self.config.height = size.height;
|
|
||||||
self.surface.configure(&self.device, &self.config);
|
|
||||||
self.ui.resize(size, &self.queue);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_encoder(device: &Device) -> CommandEncoder {
|
|
||||||
device.create_command_encoder(&CommandEncoderDescriptor {
|
|
||||||
label: Some("Render Encoder"),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new(window: Arc<Window>) -> Self {
|
|
||||||
let size = window.inner_size();
|
|
||||||
|
|
||||||
let instance = Instance::new(&InstanceDescriptor {
|
|
||||||
backends: Backends::PRIMARY,
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
|
|
||||||
let surface = instance
|
|
||||||
.create_surface(window.clone())
|
|
||||||
.expect("Could not create window surface!");
|
|
||||||
|
|
||||||
let adapter = instance
|
|
||||||
.request_adapter(&RequestAdapterOptions {
|
|
||||||
power_preference: PowerPreference::default(),
|
|
||||||
compatible_surface: Some(&surface),
|
|
||||||
force_fallback_adapter: false,
|
|
||||||
})
|
|
||||||
.block_on()
|
|
||||||
.expect("Could not get adapter!");
|
|
||||||
|
|
||||||
let ui_limits = UiLimits::default();
|
|
||||||
|
|
||||||
let (device, queue) = adapter
|
|
||||||
.request_device(&DeviceDescriptor {
|
|
||||||
required_features: Features::TEXTURE_BINDING_ARRAY
|
|
||||||
| Features::PARTIALLY_BOUND_BINDING_ARRAY
|
|
||||||
| Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING,
|
|
||||||
required_limits: Limits {
|
|
||||||
max_binding_array_elements_per_shader_stage: ui_limits
|
|
||||||
.max_binding_array_elements_per_shader_stage(),
|
|
||||||
max_binding_array_sampler_elements_per_shader_stage: ui_limits
|
|
||||||
.max_binding_array_sampler_elements_per_shader_stage(),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.block_on()
|
|
||||||
.expect("Could not get device!");
|
|
||||||
|
|
||||||
let surface_caps = surface.get_capabilities(&adapter);
|
|
||||||
let surface_format = surface_caps
|
|
||||||
.formats
|
|
||||||
.iter()
|
|
||||||
.copied()
|
|
||||||
.find(|f| f.is_srgb())
|
|
||||||
.unwrap_or(surface_caps.formats[0]);
|
|
||||||
|
|
||||||
let config = SurfaceConfiguration {
|
|
||||||
usage: TextureUsages::RENDER_ATTACHMENT,
|
|
||||||
format: surface_format,
|
|
||||||
width: size.width,
|
|
||||||
height: size.height,
|
|
||||||
present_mode: PresentMode::AutoVsync,
|
|
||||||
alpha_mode: surface_caps.alpha_modes[0],
|
|
||||||
desired_maximum_frame_latency: 2,
|
|
||||||
view_formats: vec![],
|
|
||||||
};
|
|
||||||
|
|
||||||
surface.configure(&device, &config);
|
|
||||||
|
|
||||||
let staging_belt = StagingBelt::new(4096 * 4);
|
|
||||||
let encoder = Self::create_encoder(&device);
|
|
||||||
|
|
||||||
let shape_pipeline = UiRenderer::new(&device, &queue, &config, ui_limits);
|
|
||||||
|
|
||||||
Self {
|
|
||||||
surface,
|
|
||||||
device,
|
|
||||||
queue,
|
|
||||||
config,
|
|
||||||
encoder,
|
|
||||||
staging_belt,
|
|
||||||
ui: shape_pipeline,
|
|
||||||
window,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn window(&self) -> &Window {
|
|
||||||
self.window.as_ref()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +1,11 @@
|
|||||||
pub const CLIENT_DATA: &str = "client_data";
|
pub const CLIENT_DATA: &str = "client_data";
|
||||||
|
|
||||||
#[derive(bincode::Encode, bincode::Decode)]
|
#[derive(Debug, Default, bincode::Encode, bincode::Decode)]
|
||||||
pub struct ClientData {
|
pub struct ClientData {
|
||||||
pub ip: String,
|
pub ip: String,
|
||||||
pub username: String,
|
pub username: String,
|
||||||
}
|
/// TODO: not store this as plain string?
|
||||||
|
/// need to figure out crypto stuff
|
||||||
impl Default for ClientData {
|
/// or store session token
|
||||||
fn default() -> Self {
|
pub password: String,
|
||||||
Self {
|
|
||||||
ip: "localhost:39420".to_string(),
|
|
||||||
username: "your [NOVEMBER]".to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
51
src/bin/client/state.rs
Normal file
51
src/bin/client/state.rs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
use iris::prelude::*;
|
||||||
|
use openworm::net::NetServerMsg;
|
||||||
|
use std::thread::JoinHandle;
|
||||||
|
|
||||||
|
use crate::net::NetHandle;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Connect {
|
||||||
|
pub handle: Option<JoinHandle<()>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Login {
|
||||||
|
pub handle: NetHandle,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct LoggedIn {
|
||||||
|
pub network: NetHandle,
|
||||||
|
pub msgs: Vec<NetServerMsg>,
|
||||||
|
pub channel: Option<WidgetRef<Span>>,
|
||||||
|
pub username: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum ClientState {
|
||||||
|
Connect(Connect),
|
||||||
|
Login(Login),
|
||||||
|
LoggedIn(LoggedIn),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ClientState {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Connect(Default::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ClientState {
|
||||||
|
pub fn take(&mut self) -> Self {
|
||||||
|
std::mem::take(self)
|
||||||
|
}
|
||||||
|
pub fn exit(&mut self) {
|
||||||
|
let s = self.take();
|
||||||
|
match s {
|
||||||
|
ClientState::Connect(_) => (),
|
||||||
|
ClientState::Login(Login { handle }) => {
|
||||||
|
handle.exit();
|
||||||
|
}
|
||||||
|
ClientState::LoggedIn(state) => {
|
||||||
|
state.network.exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,190 +0,0 @@
|
|||||||
use iris::prelude::*;
|
|
||||||
use len_fns::*;
|
|
||||||
use openworm::net::{ClientMsg, Msg};
|
|
||||||
use winit::dpi::{LogicalPosition, LogicalSize};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
Client,
|
|
||||||
net::{ConnectInfo, NetSender, connect},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Eq, PartialEq, Hash, Clone)]
|
|
||||||
pub struct Submit;
|
|
||||||
|
|
||||||
#[derive(Eq, PartialEq, Hash, Clone)]
|
|
||||||
pub struct Edited;
|
|
||||||
|
|
||||||
impl DefaultEvent for Submit {
|
|
||||||
type Data = ();
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DefaultEvent for Edited {
|
|
||||||
type Data = ();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn init(client: &mut Client) {
|
|
||||||
login_screen(client).set_root(&mut client.ui);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn main_view(client: &mut Client, network: NetSender) -> WidgetId {
|
|
||||||
let msg_panel = msg_panel(client, network);
|
|
||||||
let side_bar = rect(Color::BLACK.brighter(0.05)).width(80);
|
|
||||||
|
|
||||||
(side_bar, msg_panel)
|
|
||||||
.span(Dir::RIGHT)
|
|
||||||
.add(&mut client.ui)
|
|
||||||
.any()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn error(ui: &mut Ui, msg: &str) -> WidgetId {
|
|
||||||
text(msg)
|
|
||||||
.size(20)
|
|
||||||
.color(Color::RED.brighter(0.3))
|
|
||||||
.pad(10)
|
|
||||||
.add(ui)
|
|
||||||
.any()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn login_screen(client: &mut Client) -> WidgetId {
|
|
||||||
let Client {
|
|
||||||
ui, handle, data, ..
|
|
||||||
} = client;
|
|
||||||
|
|
||||||
let mut field = |name| text(name).editable().size(20).add(ui);
|
|
||||||
let ip = field(&data.ip);
|
|
||||||
let username = field(&data.username);
|
|
||||||
// let password = field("password");
|
|
||||||
|
|
||||||
let fbx = |field: WidgetId<TextEdit>| {
|
|
||||||
field
|
|
||||||
.clone()
|
|
||||||
.pad(10)
|
|
||||||
.background(rect(Color::BLACK.brighter(0.1)).radius(15))
|
|
||||||
.on(CursorSense::click(), focus_other(field))
|
|
||||||
};
|
|
||||||
|
|
||||||
// I LAV NOT HAVING ERGONOMIC CLONES
|
|
||||||
let handle = handle.clone();
|
|
||||||
let ip_ = ip.clone();
|
|
||||||
let username_ = username.clone();
|
|
||||||
let color = Color::GREEN;
|
|
||||||
let submit = rect(color)
|
|
||||||
.radius(15)
|
|
||||||
.id_on(CursorSense::click(), move |id, client: &mut Client, _| {
|
|
||||||
client.ui[id].color = color.darker(0.3);
|
|
||||||
let ip = client.ui[&ip_].content();
|
|
||||||
let username = client.ui[&username_].content();
|
|
||||||
connect(handle.clone(), ConnectInfo { ip, username });
|
|
||||||
})
|
|
||||||
.height(40);
|
|
||||||
let modal = (
|
|
||||||
text("login").size(30),
|
|
||||||
fbx(ip
|
|
||||||
.id_on(Edited, |id, client: &mut Client, _| {
|
|
||||||
client.data.ip = client.ui[id].content();
|
|
||||||
})
|
|
||||||
.add(ui)),
|
|
||||||
fbx(username
|
|
||||||
.id_on(Edited, |id, client: &mut Client, _| {
|
|
||||||
client.data.username = client.ui[id].content();
|
|
||||||
})
|
|
||||||
.add(ui)),
|
|
||||||
// fbx(password),
|
|
||||||
submit,
|
|
||||||
)
|
|
||||||
.span(Dir::DOWN)
|
|
||||||
.gap(10)
|
|
||||||
.pad(15)
|
|
||||||
.background(rect(Color::BLACK.brighter(0.2)).radius(15))
|
|
||||||
.width(400)
|
|
||||||
.align(Align::Center);
|
|
||||||
|
|
||||||
let err = WidgetPtr::default().add(ui);
|
|
||||||
client.error = Some(err.clone());
|
|
||||||
|
|
||||||
(modal, err.align(Align::Top)).stack().add(ui).any()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn msg_widget(msg: Msg) -> impl WidgetLike<FnTag> {
|
|
||||||
let content = text(msg.content)
|
|
||||||
.editable()
|
|
||||||
.size(20)
|
|
||||||
.text_align(Align::Left)
|
|
||||||
.wrap(true)
|
|
||||||
.id_on(CursorSense::click(), |i, c, d| focus(i.clone(), c, d));
|
|
||||||
let header = text(msg.user).size(20).text_align(Align::Left);
|
|
||||||
(
|
|
||||||
image(include_bytes!("./assets/sungals.png"))
|
|
||||||
.sized((70, 70))
|
|
||||||
.align(Align::TopLeft),
|
|
||||||
(
|
|
||||||
header.width(rest(1)).align(Align::Top),
|
|
||||||
content.width(rest(1)).align(Align::Top),
|
|
||||||
)
|
|
||||||
.span(Dir::DOWN)
|
|
||||||
.gap(10),
|
|
||||||
)
|
|
||||||
.span(Dir::RIGHT)
|
|
||||||
.gap(10)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn focus(id: WidgetId<TextEdit>, client: &mut Client, data: CursorData) {
|
|
||||||
client.ui.text(&id).select(data.cursor, data.size);
|
|
||||||
if let Some(region) = client.ui.window_region(&id) {
|
|
||||||
client.handle.window.set_ime_allowed(true);
|
|
||||||
client.handle.window.set_ime_cursor_area(
|
|
||||||
LogicalPosition::<f32>::from(region.top_left.tuple()),
|
|
||||||
LogicalSize::<f32>::from(region.size().tuple()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
client.focus = Some(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn focus_other(id: WidgetId<TextEdit>) -> impl Fn(&mut Client, CursorData) {
|
|
||||||
move |client: &mut Client, data: CursorData| {
|
|
||||||
focus(id.clone(), client, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn msg_panel(client: &mut Client, network: NetSender) -> impl WidgetFn<Stack> + use<> {
|
|
||||||
let Client { ui, channel, .. } = client;
|
|
||||||
let msg_area = Span::empty(Dir::DOWN).gap(15).add(ui);
|
|
||||||
*channel = Some(msg_area.clone());
|
|
||||||
|
|
||||||
let send_text = text("")
|
|
||||||
.editable()
|
|
||||||
.size(20)
|
|
||||||
.wrap(true)
|
|
||||||
.text_align(Align::Left)
|
|
||||||
.add(ui);
|
|
||||||
|
|
||||||
(
|
|
||||||
msg_area
|
|
||||||
.clone()
|
|
||||||
.align(Align::BotLeft)
|
|
||||||
.scroll()
|
|
||||||
.pad(Padding::x(15))
|
|
||||||
.height(rest(1)),
|
|
||||||
send_text
|
|
||||||
.clone()
|
|
||||||
.id_on(Submit, move |id, client: &mut Client, _| {
|
|
||||||
let content = client.ui.text(id).take();
|
|
||||||
let msg = Msg {
|
|
||||||
content: content.clone(),
|
|
||||||
user: client.username.clone(),
|
|
||||||
};
|
|
||||||
network.send(ClientMsg::SendMsg(msg.clone()));
|
|
||||||
let msg = msg_widget(msg).add(&mut client.ui);
|
|
||||||
client.ui[&msg_area].children.push(msg.any());
|
|
||||||
})
|
|
||||||
.pad(15)
|
|
||||||
.on(CursorSense::click(), focus_other(send_text))
|
|
||||||
.background(rect(Color::BLACK.brighter(0.05)).radius(15))
|
|
||||||
.pad(15)
|
|
||||||
.height(80)
|
|
||||||
.z_offset(1),
|
|
||||||
)
|
|
||||||
.span(Dir::DOWN)
|
|
||||||
.width(rest(1))
|
|
||||||
.background(rect(Color::BLACK.brighter(0.1)))
|
|
||||||
}
|
|
||||||
4
src/bin/client/ui/color.rs
Normal file
4
src/bin/client/ui/color.rs
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub const MODAL_BG: UiColor = UiColor::BLACK.brighter(0.05);
|
||||||
|
pub const GREEN: UiColor = UiColor::rgb(0, 150, 0);
|
||||||
151
src/bin/client/ui/connect.rs
Normal file
151
src/bin/client/ui/connect.rs
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub fn start_ui(client: &mut Client, ui: &mut Ui) -> WidgetRef {
|
||||||
|
let mut accounts = Span::empty(Dir::DOWN);
|
||||||
|
|
||||||
|
accounts.push(
|
||||||
|
wtext("no accounts")
|
||||||
|
.size(20)
|
||||||
|
.center_text()
|
||||||
|
.color(Color::GRAY)
|
||||||
|
.height(60)
|
||||||
|
.add(ui),
|
||||||
|
);
|
||||||
|
|
||||||
|
let connect = Button::submit("connect", ui);
|
||||||
|
let create = Button::submit("create", ui);
|
||||||
|
let connect_ = connect.clone();
|
||||||
|
let create_ = create.clone();
|
||||||
|
ui.on(connect.view(), Submit, move |_| {
|
||||||
|
connect_.disable();
|
||||||
|
create_.disable();
|
||||||
|
});
|
||||||
|
|
||||||
|
let connect_ = connect.clone();
|
||||||
|
let create_ = create.clone();
|
||||||
|
ui.on(create.view(), Submit, move |ctx| {
|
||||||
|
connect_.disable();
|
||||||
|
create_.disable();
|
||||||
|
create_ui(ctx.ui).set_ptr(&ctx.state.main_ui, ctx.ui);
|
||||||
|
});
|
||||||
|
|
||||||
|
(
|
||||||
|
wtext("Select Account").text_align(Align::CENTER).size(30),
|
||||||
|
accounts,
|
||||||
|
(connect, create).span(Dir::RIGHT).gap(10),
|
||||||
|
)
|
||||||
|
.span(Dir::DOWN)
|
||||||
|
.gap(30)
|
||||||
|
.modal(400)
|
||||||
|
.add(ui)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_ui(ui: &mut Ui) -> WidgetRef {
|
||||||
|
wtext("hi").add(ui)
|
||||||
|
}
|
||||||
|
|
||||||
|
// pub fn connect_screen(client: &mut Client, ui: &mut Ui, state: &UiState) -> WidgetRef {
|
||||||
|
// let Client { data, proxy, .. } = client;
|
||||||
|
// let ip = field_widget(&data.ip, "ip", ui);
|
||||||
|
// let ip_ = ip.clone();
|
||||||
|
// let handle = AppHandle {
|
||||||
|
// proxy: proxy.clone(),
|
||||||
|
// window: state.window.clone(),
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// let submit = Button::submit("connect", ui);
|
||||||
|
//
|
||||||
|
// submit.on(Submit, move |ctx| {
|
||||||
|
// let ClientState::Connect(state) = &mut ctx.state.state else {
|
||||||
|
// return;
|
||||||
|
// };
|
||||||
|
// let ip = ip_.get().content();
|
||||||
|
// state.handle = Some(connect(handle.clone(), ConnectInfo { ip }));
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// (
|
||||||
|
// wtext("connect to a server")
|
||||||
|
// .text_align(Align::CENTER)
|
||||||
|
// .size(30),
|
||||||
|
// field_box(
|
||||||
|
// // NOTE: should probably do this on submit
|
||||||
|
// ip.on(Edited, |ctx| {
|
||||||
|
// ctx.state.data.ip = ctx.widget.get().content();
|
||||||
|
// })
|
||||||
|
// .add(ui),
|
||||||
|
// ui,
|
||||||
|
// ),
|
||||||
|
// submit,
|
||||||
|
// )
|
||||||
|
// .span(Dir::DOWN)
|
||||||
|
// .gap(10)
|
||||||
|
// .pad(15)
|
||||||
|
// .background(rect(Color::BLACK.brighter(0.2)).radius(15))
|
||||||
|
// .width(400)
|
||||||
|
// .align(Align::CENTER)
|
||||||
|
// .add(ui)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// pub fn login_screen(client: &mut Client, ui: &mut Ui) -> WidgetRef {
|
||||||
|
// let Client { data, .. } = client;
|
||||||
|
// let username = field_widget(&data.username, "username", ui);
|
||||||
|
// let password = field_widget(&data.password, "password", ui);
|
||||||
|
// let username_ = username.clone();
|
||||||
|
// let password_ = password.clone();
|
||||||
|
// let submit = Button::submit("login", ui);
|
||||||
|
// submit
|
||||||
|
// .on(move |client, _ui| {
|
||||||
|
// let ClientState::Login(state) = &mut client.state else {
|
||||||
|
// return;
|
||||||
|
// };
|
||||||
|
// let username = username_.get().content();
|
||||||
|
// let password = password_.get().content();
|
||||||
|
// state.handle.send(ClientMsg::Login { username, password });
|
||||||
|
// })
|
||||||
|
// .add(ui);
|
||||||
|
// let username_ = username.clone();
|
||||||
|
// let password_ = password.clone();
|
||||||
|
// let create_button = Button::submit(
|
||||||
|
// "create account",
|
||||||
|
// move |client, _ui| {
|
||||||
|
// let ClientState::Login(state) = &mut client.state else {
|
||||||
|
// return;
|
||||||
|
// };
|
||||||
|
// let username = username_.get().content();
|
||||||
|
// let password = password_.get().content();
|
||||||
|
// state
|
||||||
|
// .handle
|
||||||
|
// .send(ClientMsg::CreateAccount { username, password });
|
||||||
|
// },
|
||||||
|
// ui,
|
||||||
|
// );
|
||||||
|
// create_button.on()
|
||||||
|
// (
|
||||||
|
// wtext("login to server").text_align(Align::CENTER).size(30),
|
||||||
|
// field_box(
|
||||||
|
// username
|
||||||
|
// .on(Edited, |ctx| {
|
||||||
|
// ctx.state.data.username = ctx.widget.get().content();
|
||||||
|
// })
|
||||||
|
// .add(ui),
|
||||||
|
// ui,
|
||||||
|
// ),
|
||||||
|
// field_box(
|
||||||
|
// password
|
||||||
|
// .on(Edited, |ctx| {
|
||||||
|
// ctx.state.data.password = ctx.widget.get().content();
|
||||||
|
// })
|
||||||
|
// .add(ui),
|
||||||
|
// ui,
|
||||||
|
// ),
|
||||||
|
// submit,
|
||||||
|
// create_button,
|
||||||
|
// )
|
||||||
|
// .span(Dir::DOWN)
|
||||||
|
// .gap(10)
|
||||||
|
// .pad(15)
|
||||||
|
// .background(rect(Color::BLACK.brighter(0.2)).radius(15))
|
||||||
|
// .width(400)
|
||||||
|
// .align(Align::CENTER)
|
||||||
|
// .add(ui)
|
||||||
|
// }
|
||||||
82
src/bin/client/ui/main.rs
Normal file
82
src/bin/client/ui/main.rs
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
use super::*;
|
||||||
|
use crate::state::{ClientState, LoggedIn};
|
||||||
|
use openworm::net::{ClientMsg, NetClientMsg};
|
||||||
|
|
||||||
|
pub const SIZE: u32 = 20;
|
||||||
|
|
||||||
|
pub fn main_view(client: &mut Client, ui: &mut Ui) -> WidgetRef {
|
||||||
|
let ClientState::LoggedIn(state) = &mut client.state else {
|
||||||
|
panic!("we ain't logged in buh");
|
||||||
|
};
|
||||||
|
let msg_panel = msg_panel(ui, state);
|
||||||
|
let side_bar = rect(Color::BLACK.brighter(0.05)).width(80);
|
||||||
|
|
||||||
|
(side_bar, msg_panel).span(Dir::RIGHT).add(ui)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn msg_widget(username: &str, content: &str) -> impl WidgetRet {
|
||||||
|
let content = wtext(content)
|
||||||
|
.editable(false)
|
||||||
|
.size(SIZE)
|
||||||
|
.wrap(true)
|
||||||
|
.attr::<Selectable>(());
|
||||||
|
let header = wtext(username).size(SIZE);
|
||||||
|
(
|
||||||
|
image(include_bytes!("../assets/sungals.png"))
|
||||||
|
.sized((70, 70))
|
||||||
|
.align(Align::TOP),
|
||||||
|
(header, content)
|
||||||
|
.span(Dir::DOWN)
|
||||||
|
.gap(10)
|
||||||
|
.width(rest(1))
|
||||||
|
.align(Align::TOP),
|
||||||
|
)
|
||||||
|
.span(Dir::RIGHT)
|
||||||
|
.gap(10)
|
||||||
|
.to_any()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn msg_panel(ui: &mut Ui, state: &mut LoggedIn) -> WidgetRef {
|
||||||
|
let msg_area = Span::empty(Dir::DOWN).gap(15).add(ui);
|
||||||
|
state.channel = Some(msg_area.clone());
|
||||||
|
|
||||||
|
let send_text = wtext("")
|
||||||
|
.editable(false)
|
||||||
|
.size(SIZE)
|
||||||
|
.wrap(true)
|
||||||
|
.hint(hint("send message"))
|
||||||
|
.add(ui);
|
||||||
|
|
||||||
|
(
|
||||||
|
msg_area
|
||||||
|
.clone()
|
||||||
|
.scroll()
|
||||||
|
.pad(Padding::x(15).with_top(15))
|
||||||
|
.height(rest(1)),
|
||||||
|
send_text
|
||||||
|
.clone()
|
||||||
|
.on(Submit, move |ctx| {
|
||||||
|
let ClientState::LoggedIn(state) = &mut ctx.state.state else {
|
||||||
|
panic!("we ain't logged in buh");
|
||||||
|
};
|
||||||
|
let content = ctx.widget.get_mut().take();
|
||||||
|
let msg = NetClientMsg {
|
||||||
|
content: content.clone(),
|
||||||
|
};
|
||||||
|
state.network.send(ClientMsg::SendMsg(msg.clone()));
|
||||||
|
let msg = msg_widget(&state.username, &content).add(ctx.ui);
|
||||||
|
msg_area.get_mut().children.push(msg);
|
||||||
|
})
|
||||||
|
.pad(15)
|
||||||
|
.attr::<Selector>(send_text)
|
||||||
|
.scroll()
|
||||||
|
.masked()
|
||||||
|
.background(rect(Color::BLACK.brighter(0.05)).radius(15))
|
||||||
|
.pad(15)
|
||||||
|
.max_height(rel(0.5))
|
||||||
|
.layer_offset(1),
|
||||||
|
)
|
||||||
|
.span(Dir::DOWN)
|
||||||
|
.width(rest(1))
|
||||||
|
.add(ui)
|
||||||
|
}
|
||||||
113
src/bin/client/ui/misc.rs
Normal file
113
src/bin/client/ui/misc.rs
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub fn werror(ui: &mut Ui, msg: &str) -> WidgetRef {
|
||||||
|
wtext(msg)
|
||||||
|
.size(20)
|
||||||
|
.pad(10)
|
||||||
|
.background(rect(Color::RED).radius(10))
|
||||||
|
.add(ui)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hint(msg: impl Into<String>) -> TextBuilder {
|
||||||
|
wtext(msg).size(20).color(Color::GRAY)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn field_widget(name: &str, hint_text: &str, ui: &mut Ui) -> WidgetRef<TextEdit> {
|
||||||
|
wtext(name)
|
||||||
|
.editable(true)
|
||||||
|
.size(20)
|
||||||
|
.hint(hint(hint_text))
|
||||||
|
.add(ui)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn field_box(field: WidgetRef<TextEdit>, ui: &mut Ui) -> WidgetRef {
|
||||||
|
field
|
||||||
|
.clone()
|
||||||
|
.pad(10)
|
||||||
|
.background(rect(Color::BLACK.brighter(0.1)).radius(15))
|
||||||
|
.attr::<Selector>(field)
|
||||||
|
.add(ui)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Button {
|
||||||
|
color: UiColor,
|
||||||
|
root: WidgetRef,
|
||||||
|
rect: WidgetRef<Rect>,
|
||||||
|
enabled: Handle<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WidgetView for Button {
|
||||||
|
fn view(&self) -> &WidgetRef<Self::Widget> {
|
||||||
|
&self.root
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Button {
|
||||||
|
pub fn new(text: &str, color: UiColor, ui: &mut Ui) -> Self {
|
||||||
|
let rect = rect(color).radius(15).add(ui);
|
||||||
|
let enabled = Handle::from(true);
|
||||||
|
let enabled_ = enabled.clone();
|
||||||
|
let enabled__ = enabled.clone();
|
||||||
|
let root = rect
|
||||||
|
.clone()
|
||||||
|
.on(
|
||||||
|
CursorSense::HoverStart | CursorSense::unclick(),
|
||||||
|
move |ctx| {
|
||||||
|
if !*enabled_.get() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ctx.widget.get_mut().color = color.brighter(0.1);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.on(CursorSense::HoverEnd, move |ctx| {
|
||||||
|
if !*enabled__.get() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ctx.widget.get_mut().color = color;
|
||||||
|
})
|
||||||
|
.height(60)
|
||||||
|
.foreground(wtext(text).size(25).text_align(Align::CENTER))
|
||||||
|
.add(ui);
|
||||||
|
let root_ = root.clone();
|
||||||
|
let enabled_ = enabled.clone();
|
||||||
|
rect.clone()
|
||||||
|
.on(CursorSense::click(), move |ctx| {
|
||||||
|
if !*enabled_.get() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ctx.widget.get_mut().color = color.darker(0.2);
|
||||||
|
ctx.ui.run_event(ctx.state, &root_, Submit, ());
|
||||||
|
})
|
||||||
|
.add(ui);
|
||||||
|
Self {
|
||||||
|
root,
|
||||||
|
rect,
|
||||||
|
color,
|
||||||
|
enabled,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn submit(text: &str, ui: &mut Ui) -> Self {
|
||||||
|
Self::new(text, color::GREEN, ui)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn disable(&self) {
|
||||||
|
*self.enabled.get_mut() = false;
|
||||||
|
self.rect.get_mut().color = self.color.darker(0.8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
widget_trait! {
|
||||||
|
pub trait Stuff;
|
||||||
|
fn modal(self, width: impl UiNum) -> impl WidgetIdFn {
|
||||||
|
|ui| {
|
||||||
|
self
|
||||||
|
.pad(15)
|
||||||
|
.background(rect(color::MODAL_BG).radius(15))
|
||||||
|
.width(width)
|
||||||
|
.align(Align::CENTER)
|
||||||
|
.add(ui)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
src/bin/client/ui/mod.rs
Normal file
14
src/bin/client/ui/mod.rs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
use crate::Client;
|
||||||
|
use iris::prelude::*;
|
||||||
|
use len_fns::*;
|
||||||
|
|
||||||
|
mod connect;
|
||||||
|
mod main;
|
||||||
|
mod misc;
|
||||||
|
pub mod color;
|
||||||
|
|
||||||
|
pub use connect::*;
|
||||||
|
pub use main::*;
|
||||||
|
pub use misc::*;
|
||||||
|
|
||||||
|
event_ctx!(Client);
|
||||||
3
src/bin/server/data.rs
Normal file
3
src/bin/server/data.rs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
pub struct DBMsg {
|
||||||
|
|
||||||
|
}
|
||||||
167
src/bin/server/db.rs
Normal file
167
src/bin/server/db.rs
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
use std::{
|
||||||
|
marker::PhantomData,
|
||||||
|
ops::{Deref, DerefMut},
|
||||||
|
path::Path,
|
||||||
|
};
|
||||||
|
|
||||||
|
use bincode::{Decode, Encode};
|
||||||
|
use openworm::net::BINCODE_CONFIG;
|
||||||
|
use sled::Tree;
|
||||||
|
|
||||||
|
pub const DB_VERSION: u64 = 0;
|
||||||
|
|
||||||
|
#[derive(Encode, Decode)]
|
||||||
|
pub struct User {
|
||||||
|
pub username: String,
|
||||||
|
pub password_hash: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Encode, Decode)]
|
||||||
|
pub struct Msg {
|
||||||
|
pub user: u64,
|
||||||
|
pub content: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Db {
|
||||||
|
pub db: sled::Db,
|
||||||
|
pub msgs: DbMap<u64, Msg>,
|
||||||
|
pub users: DbMap<u64, User>,
|
||||||
|
pub usernames: DbMap<String, u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DbMap<K, V> {
|
||||||
|
tree: Tree,
|
||||||
|
_pd: PhantomData<(K, V)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Key {
|
||||||
|
type Output<'a>: AsRef<[u8]>
|
||||||
|
where
|
||||||
|
Self: 'a;
|
||||||
|
fn bytes(&self) -> Self::Output<'_>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Key for String {
|
||||||
|
type Output<'a> = &'a Self;
|
||||||
|
fn bytes(&self) -> Self::Output<'_> {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Key for str {
|
||||||
|
type Output<'a> = &'a Self;
|
||||||
|
fn bytes(&self) -> Self::Output<'_> {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Key for u64 {
|
||||||
|
type Output<'a> = [u8; 8];
|
||||||
|
|
||||||
|
fn bytes(&self) -> Self::Output<'_> {
|
||||||
|
self.to_be_bytes()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<K: Key, V: Encode + Decode<()>> DbMap<K, V> {
|
||||||
|
pub fn insert(&self, k: &K, v: &V) {
|
||||||
|
self.tree.insert_(k, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, k: &K) -> Option<V> {
|
||||||
|
self.tree.get_(k)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init_unique(&self, k: &K) -> bool {
|
||||||
|
self.tree
|
||||||
|
.compare_and_swap(k.bytes(), None as Option<&[u8]>, Some(&[0]))
|
||||||
|
.unwrap()
|
||||||
|
.is_ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iter_all(&self) -> impl Iterator<Item = V> {
|
||||||
|
self.tree.iter_all()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn open_db(path: impl AsRef<Path>) -> Db {
|
||||||
|
let db = sled::open(path).expect("failed to open database");
|
||||||
|
if !db.was_recovered() {
|
||||||
|
println!("no previous db found, creating new");
|
||||||
|
db.insert_("version", DB_VERSION);
|
||||||
|
db.flush().unwrap();
|
||||||
|
} else {
|
||||||
|
let version: u64 = db.get_("version").expect("failed to read db version");
|
||||||
|
println!("found existing db version {version}");
|
||||||
|
if version != DB_VERSION {
|
||||||
|
panic!("non matching db version! (auto update in the future)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Db {
|
||||||
|
msgs: open_tree("msg", &db),
|
||||||
|
users: open_tree("user", &db),
|
||||||
|
usernames: open_tree("username", &db),
|
||||||
|
db,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trait DbUtil {
|
||||||
|
fn insert_<V: Encode>(&self, k: &(impl Key + ?Sized), v: V);
|
||||||
|
fn get_<V: Decode<()>>(&self, k: &(impl Key + ?Sized)) -> Option<V>;
|
||||||
|
fn iter_all<V: Decode<()>>(&self) -> impl Iterator<Item = V>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DbUtil for Tree {
|
||||||
|
fn insert_<V: Encode>(&self, k: &(impl Key + ?Sized), v: V) {
|
||||||
|
let bytes = bincode::encode_to_vec(v, BINCODE_CONFIG).unwrap();
|
||||||
|
self.insert(k.bytes(), bytes).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_<V: Decode<()>>(&self, k: &(impl Key + ?Sized)) -> Option<V> {
|
||||||
|
let bytes = self.get(k.bytes()).unwrap()?;
|
||||||
|
Some(
|
||||||
|
bincode::decode_from_slice(&bytes, BINCODE_CONFIG)
|
||||||
|
.unwrap()
|
||||||
|
.0,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn iter_all<V: Decode<()>>(&self) -> impl Iterator<Item = V> {
|
||||||
|
self.iter().map(|r| {
|
||||||
|
bincode::decode_from_slice(&r.unwrap().1, BINCODE_CONFIG)
|
||||||
|
.unwrap()
|
||||||
|
.0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn open_tree<K, V>(name: &str, db: &sled::Db) -> DbMap<K, V> {
|
||||||
|
DbMap {
|
||||||
|
tree: db.open_tree(name).unwrap(),
|
||||||
|
_pd: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for Db {
|
||||||
|
type Target = sled::Db;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.db
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DerefMut for Db {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.db
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<K, V> Clone for DbMap<K, V> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
tree: self.tree.clone(),
|
||||||
|
_pd: self._pd,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,21 @@
|
|||||||
|
// mod data;
|
||||||
|
mod db;
|
||||||
mod net;
|
mod net;
|
||||||
|
|
||||||
|
use crate::db::{Db, Msg, User, open_db};
|
||||||
|
use clap::Parser;
|
||||||
use net::{ClientSender, ConAccepter, listen};
|
use net::{ClientSender, ConAccepter, listen};
|
||||||
use openworm::{
|
use openworm::{
|
||||||
net::{ClientMsg, DisconnectReason, Msg, RecvHandler, ServerMsg, install_crypto_provider},
|
net::{
|
||||||
|
ClientMsg, DisconnectReason, NetServerMsg, RecvHandler, ServerError, ServerMsg,
|
||||||
|
install_crypto_provider,
|
||||||
|
},
|
||||||
rsc::DataDir,
|
rsc::DataDir,
|
||||||
};
|
};
|
||||||
|
use scrypt::{
|
||||||
|
Scrypt,
|
||||||
|
password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString, rand_core::OsRng},
|
||||||
|
};
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
sync::{
|
sync::{
|
||||||
@@ -12,40 +23,66 @@ use std::{
|
|||||||
atomic::{AtomicU64, Ordering},
|
atomic::{AtomicU64, Ordering},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use tokio::sync::RwLock;
|
use tokio::{signal::ctrl_c, sync::RwLock};
|
||||||
|
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
#[command(version, about, long_about = None)]
|
||||||
|
struct Args {
|
||||||
|
/// port to listen on
|
||||||
|
#[arg(short, long)]
|
||||||
|
port: u16,
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
let args = Args::parse();
|
||||||
install_crypto_provider();
|
install_crypto_provider();
|
||||||
run_server();
|
run_server(args.port);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
pub async fn run_server() {
|
pub async fn run_server(port: u16) {
|
||||||
let dir = DataDir::default();
|
let dir = DataDir::default();
|
||||||
let path = dir.get();
|
let path = dir.get();
|
||||||
|
let db: Db = open_db(path.join("server.db"));
|
||||||
let handler = ServerListener {
|
let handler = ServerListener {
|
||||||
msgs: Default::default(),
|
|
||||||
senders: Default::default(),
|
senders: Default::default(),
|
||||||
count: 0.into(),
|
count: 0.into(),
|
||||||
|
db: db.clone(),
|
||||||
};
|
};
|
||||||
listen(path, handler).await;
|
let (endpoint, handle) = listen(port, path, handler);
|
||||||
|
let _ = ctrl_c().await;
|
||||||
|
println!("stopping server");
|
||||||
|
println!("closing connections...");
|
||||||
|
endpoint.close(0u32.into(), &[]);
|
||||||
|
let _ = handle.await;
|
||||||
|
endpoint.wait_idle().await;
|
||||||
|
println!("saving...");
|
||||||
|
db.flush_async().await.unwrap();
|
||||||
|
println!("saved");
|
||||||
}
|
}
|
||||||
|
|
||||||
type ClientId = u64;
|
type ClientId = u64;
|
||||||
|
|
||||||
struct ServerListener {
|
struct ServerListener {
|
||||||
msgs: Arc<RwLock<Vec<Msg>>>,
|
db: Db,
|
||||||
senders: Arc<RwLock<HashMap<ClientId, ClientSender>>>,
|
senders: Arc<RwLock<HashMap<ClientId, ClientSender>>>,
|
||||||
count: AtomicU64,
|
count: AtomicU64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Clone, Copy)]
|
||||||
|
pub enum ClientState {
|
||||||
|
Login,
|
||||||
|
Authed(u64),
|
||||||
|
}
|
||||||
|
|
||||||
impl ConAccepter for ServerListener {
|
impl ConAccepter for ServerListener {
|
||||||
async fn accept(&self, send: ClientSender) -> impl RecvHandler<ClientMsg> {
|
async fn accept(&self, send: ClientSender) -> impl RecvHandler<ClientMsg> {
|
||||||
let id = self.count.fetch_add(1, Ordering::Release);
|
let id = self.count.fetch_add(1, Ordering::Release);
|
||||||
self.senders.write().await.insert(id, send.clone());
|
self.senders.write().await.insert(id, send.clone());
|
||||||
ClientHandler {
|
ClientHandler {
|
||||||
msgs: self.msgs.clone(),
|
db: self.db.clone(),
|
||||||
senders: self.senders.clone(),
|
senders: self.senders.clone(),
|
||||||
|
state: Arc::new(RwLock::new(ClientState::Login)),
|
||||||
send,
|
send,
|
||||||
id,
|
id,
|
||||||
}
|
}
|
||||||
@@ -53,18 +90,36 @@ impl ConAccepter for ServerListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct ClientHandler {
|
struct ClientHandler {
|
||||||
msgs: Arc<RwLock<Vec<Msg>>>,
|
db: Db,
|
||||||
send: ClientSender,
|
send: ClientSender,
|
||||||
senders: Arc<RwLock<HashMap<ClientId, ClientSender>>>,
|
senders: Arc<RwLock<HashMap<ClientId, ClientSender>>>,
|
||||||
id: ClientId,
|
id: ClientId,
|
||||||
|
state: Arc<RwLock<ClientState>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RecvHandler<ClientMsg> for ClientHandler {
|
impl RecvHandler<ClientMsg> for ClientHandler {
|
||||||
|
async fn connect(&self) -> () {
|
||||||
|
println!("connected: {:?}", self.send.remote().ip());
|
||||||
|
}
|
||||||
async fn msg(&self, msg: ClientMsg) {
|
async fn msg(&self, msg: ClientMsg) {
|
||||||
match msg {
|
match msg {
|
||||||
ClientMsg::SendMsg(msg) => {
|
ClientMsg::SendMsg(msg) => {
|
||||||
self.msgs.write().await.push(msg.clone());
|
let ClientState::Authed(uid) = &*self.state.read().await else {
|
||||||
|
let _ = self.send.send(ServerError::NotLoggedIn).await;
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let msg = Msg {
|
||||||
|
user: *uid,
|
||||||
|
content: msg.content,
|
||||||
|
};
|
||||||
|
let id = self.db.generate_id().unwrap();
|
||||||
|
self.db.msgs.insert(&id, &msg);
|
||||||
let mut handles = Vec::new();
|
let mut handles = Vec::new();
|
||||||
|
let user: User = self.db.users.get(uid).unwrap();
|
||||||
|
let msg = NetServerMsg {
|
||||||
|
content: msg.content,
|
||||||
|
user: user.username,
|
||||||
|
};
|
||||||
for (&id, send) in self.senders.read().await.iter() {
|
for (&id, send) in self.senders.read().await.iter() {
|
||||||
if id == self.id {
|
if id == self.id {
|
||||||
continue;
|
continue;
|
||||||
@@ -72,7 +127,7 @@ impl RecvHandler<ClientMsg> for ClientHandler {
|
|||||||
let send = send.clone();
|
let send = send.clone();
|
||||||
let msg = msg.clone();
|
let msg = msg.clone();
|
||||||
let fut = async move {
|
let fut = async move {
|
||||||
let _ = send.send(ServerMsg::SendMsg(msg)).await;
|
let _ = send.send(msg).await;
|
||||||
};
|
};
|
||||||
handles.push(tokio::spawn(fut));
|
handles.push(tokio::spawn(fut));
|
||||||
}
|
}
|
||||||
@@ -81,16 +136,85 @@ impl RecvHandler<ClientMsg> for ClientHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
ClientMsg::RequestMsgs => {
|
ClientMsg::RequestMsgs => {
|
||||||
let msgs = self.msgs.read().await.clone();
|
let ClientState::Authed(_uid) = &*self.state.read().await else {
|
||||||
|
let _ = self.send.send(ServerError::NotLoggedIn).await;
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let msgs = self
|
||||||
|
.db
|
||||||
|
.msgs
|
||||||
|
.iter_all()
|
||||||
|
.map(|msg| {
|
||||||
|
let user = self
|
||||||
|
.db
|
||||||
|
.users
|
||||||
|
.get(&msg.user)
|
||||||
|
.map(|user| user.username.to_string())
|
||||||
|
.unwrap_or("deleted user".to_string());
|
||||||
|
NetServerMsg {
|
||||||
|
content: msg.content,
|
||||||
|
user,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
let _ = self.send.send(ServerMsg::LoadMsgs(msgs)).await;
|
let _ = self.send.send(ServerMsg::LoadMsgs(msgs)).await;
|
||||||
}
|
}
|
||||||
|
ClientMsg::CreateAccount { username, password } => {
|
||||||
|
if !self.db.usernames.init_unique(&username) {
|
||||||
|
let _ = self.send.send(ServerError::UsernameTaken).await;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let id = self.db.generate_id().unwrap();
|
||||||
|
let salt = SaltString::generate(&mut OsRng);
|
||||||
|
let params = scrypt::Params::new(11, 8, 1, 32).unwrap();
|
||||||
|
let hash = Scrypt
|
||||||
|
.hash_password_customized(password.as_bytes(), None, None, params, &salt)
|
||||||
|
.unwrap()
|
||||||
|
.to_string();
|
||||||
|
self.db.users.insert(
|
||||||
|
&id,
|
||||||
|
&User {
|
||||||
|
username: username.clone(),
|
||||||
|
password_hash: hash,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
println!("account created: \"{username}\"");
|
||||||
|
self.db.usernames.insert(&username, &id);
|
||||||
|
*self.state.write().await = ClientState::Authed(id);
|
||||||
|
let _ = self.send.send(ServerMsg::Login { username }).await;
|
||||||
|
}
|
||||||
|
ClientMsg::Login { username, password } => {
|
||||||
|
let Some(id) = self.db.usernames.get(&username) else {
|
||||||
|
let _ = self.send.send(ServerError::UnknownUsername).await;
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let Some(user) = self.db.users.get(&id) else {
|
||||||
|
panic!("invalid state! (should be a user)");
|
||||||
|
};
|
||||||
|
let hash = PasswordHash::new(&user.password_hash).unwrap();
|
||||||
|
if Scrypt.verify_password(password.as_bytes(), &hash).is_err() {
|
||||||
|
println!("invalid password: \"{username}\"");
|
||||||
|
let _ = self.send.send(ServerError::InvalidPassword).await;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
println!("login: \"{username}\"");
|
||||||
|
*self.state.write().await = ClientState::Authed(id);
|
||||||
|
let _ = self.send.send(ServerMsg::Login { username }).await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn disconnect(&self, reason: DisconnectReason) -> () {
|
async fn disconnect(&self, reason: DisconnectReason) -> () {
|
||||||
|
println!("disconnected: {:?}", self.send.remote().ip());
|
||||||
match reason {
|
match reason {
|
||||||
DisconnectReason::Closed | DisconnectReason::Timeout => (),
|
DisconnectReason::Closed | DisconnectReason::Timeout => (),
|
||||||
DisconnectReason::Other(e) => println!("connection issue: {e}"),
|
DisconnectReason::Other(e) => println!("connection issue: {e}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ClientState {
|
||||||
|
pub fn is_authed(&self) -> bool {
|
||||||
|
matches!(self, Self::Authed(_))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,14 +10,12 @@ use std::{
|
|||||||
net::{Ipv6Addr, SocketAddr, SocketAddrV6},
|
net::{Ipv6Addr, SocketAddr, SocketAddrV6},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
use tokio::task::JoinHandle;
|
||||||
use tracing::Instrument;
|
use tracing::Instrument;
|
||||||
|
|
||||||
pub const DEFAULT_PORT: u16 = 16839;
|
|
||||||
pub const SERVER_HOST: Ipv6Addr = Ipv6Addr::UNSPECIFIED;
|
pub const SERVER_HOST: Ipv6Addr = Ipv6Addr::UNSPECIFIED;
|
||||||
pub const SERVER_SOCKET: SocketAddr =
|
|
||||||
SocketAddr::V6(SocketAddrV6::new(SERVER_HOST, DEFAULT_PORT, 0, 0));
|
|
||||||
|
|
||||||
pub fn init_endpoint(data_path: &Path) -> Endpoint {
|
pub fn init_endpoint(port: u16, data_path: &Path) -> Endpoint {
|
||||||
let cert_path = data_path.join("cert.der");
|
let cert_path = data_path.join("cert.der");
|
||||||
let key_path = data_path.join("key.der");
|
let key_path = data_path.join("key.der");
|
||||||
let (cert, key) = match fs::read(&cert_path).and_then(|x| Ok((x, fs::read(&key_path)?))) {
|
let (cert, key) = match fs::read(&cert_path).and_then(|x| Ok((x, fs::read(&key_path)?))) {
|
||||||
@@ -51,7 +49,8 @@ pub fn init_endpoint(data_path: &Path) -> Endpoint {
|
|||||||
// let transport_config = Arc::get_mut(&mut server_config.transport).unwrap();
|
// let transport_config = Arc::get_mut(&mut server_config.transport).unwrap();
|
||||||
// transport_config.max_concurrent_uni_streams(0_u8.into());
|
// transport_config.max_concurrent_uni_streams(0_u8.into());
|
||||||
|
|
||||||
quinn::Endpoint::server(server_config, SERVER_SOCKET).unwrap()
|
let server_socket: SocketAddr = SocketAddr::V6(SocketAddrV6::new(SERVER_HOST, port, 0, 0));
|
||||||
|
quinn::Endpoint::server(server_config, server_socket).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@@ -60,7 +59,11 @@ pub struct ClientSender {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ClientSender {
|
impl ClientSender {
|
||||||
pub async fn send(&self, msg: ServerMsg) -> SendResult {
|
pub fn remote(&self) -> SocketAddr {
|
||||||
|
self.conn.remote_address()
|
||||||
|
}
|
||||||
|
pub async fn send(&self, msg: impl Into<ServerMsg>) -> SendResult {
|
||||||
|
let msg = msg.into();
|
||||||
send_uni(&self.conn, msg).await
|
send_uni(&self.conn, msg).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -72,19 +75,31 @@ pub trait ConAccepter: Send + Sync + 'static {
|
|||||||
) -> impl Future<Output = impl RecvHandler<ClientMsg>> + Send;
|
) -> impl Future<Output = impl RecvHandler<ClientMsg>> + Send;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn listen(data_path: &Path, accepter: impl ConAccepter) {
|
pub fn listen(
|
||||||
|
port: u16,
|
||||||
|
data_path: &Path,
|
||||||
|
accepter: impl ConAccepter,
|
||||||
|
) -> (Endpoint, JoinHandle<()>) {
|
||||||
let accepter = Arc::new(accepter);
|
let accepter = Arc::new(accepter);
|
||||||
let endpoint = init_endpoint(data_path);
|
let endpoint = init_endpoint(port, data_path);
|
||||||
println!("listening on {}", endpoint.local_addr().unwrap());
|
let res = endpoint.clone();
|
||||||
|
|
||||||
|
let handle = tokio::spawn(async move {
|
||||||
|
println!("listening on {}", endpoint.local_addr().unwrap());
|
||||||
|
let mut tasks = Vec::new();
|
||||||
while let Some(conn) = endpoint.accept().await {
|
while let Some(conn) = endpoint.accept().await {
|
||||||
let fut = handle_connection(conn, accepter.clone());
|
let fut = handle_connection(conn, accepter.clone());
|
||||||
tokio::spawn(async move {
|
tasks.push(tokio::spawn(async move {
|
||||||
if let Err(e) = fut.await {
|
if let Err(e) = fut.await {
|
||||||
eprintln!("connection failed: {reason}", reason = e)
|
eprintln!("connection failed: {reason}", reason = e)
|
||||||
}
|
}
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
|
for task in tasks {
|
||||||
|
let _ = task.await;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
(res, handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_connection(
|
async fn handle_connection(
|
||||||
@@ -93,6 +108,7 @@ async fn handle_connection(
|
|||||||
) -> std::io::Result<()> {
|
) -> std::io::Result<()> {
|
||||||
let conn = conn.await?;
|
let conn = conn.await?;
|
||||||
let handler = Arc::new(accepter.accept(ClientSender { conn: conn.clone() }).await);
|
let handler = Arc::new(accepter.accept(ClientSender { conn: conn.clone() }).await);
|
||||||
|
handler.connect().await;
|
||||||
let span = tracing::info_span!(
|
let span = tracing::info_span!(
|
||||||
"connection",
|
"connection",
|
||||||
remote = %conn.remote_address(),
|
remote = %conn.remote_address(),
|
||||||
|
|||||||
@@ -11,24 +11,53 @@ pub const BINCODE_CONFIG: Configuration = bincode::config::standard();
|
|||||||
|
|
||||||
#[derive(Debug, bincode::Encode, bincode::Decode)]
|
#[derive(Debug, bincode::Encode, bincode::Decode)]
|
||||||
pub enum ClientMsg {
|
pub enum ClientMsg {
|
||||||
SendMsg(Msg),
|
SendMsg(NetClientMsg),
|
||||||
RequestMsgs,
|
RequestMsgs,
|
||||||
|
CreateAccount { username: String, password: String },
|
||||||
|
Login { username: String, password: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, bincode::Encode, bincode::Decode)]
|
#[derive(Debug, bincode::Encode, bincode::Decode)]
|
||||||
pub enum ServerMsg {
|
pub enum ServerMsg {
|
||||||
SendMsg(Msg),
|
SendMsg(NetServerMsg),
|
||||||
LoadMsgs(Vec<Msg>),
|
LoadMsgs(Vec<NetServerMsg>),
|
||||||
|
Login { username: String },
|
||||||
|
Error(ServerError),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, bincode::Encode, bincode::Decode)]
|
||||||
|
pub enum ServerError {
|
||||||
|
NotLoggedIn,
|
||||||
|
UnknownUsername,
|
||||||
|
InvalidPassword,
|
||||||
|
UsernameTaken,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ServerError> for ServerMsg {
|
||||||
|
fn from(value: ServerError) -> Self {
|
||||||
|
Self::Error(value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type ServerResp<T> = Result<T, String>;
|
pub type ServerResp<T> = Result<T, String>;
|
||||||
|
|
||||||
#[derive(Debug, Clone, bincode::Encode, bincode::Decode)]
|
#[derive(Debug, Clone, bincode::Encode, bincode::Decode)]
|
||||||
pub struct Msg {
|
pub struct NetClientMsg {
|
||||||
|
pub content: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, bincode::Encode, bincode::Decode)]
|
||||||
|
pub struct NetServerMsg {
|
||||||
pub content: String,
|
pub content: String,
|
||||||
pub user: String,
|
pub user: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<NetServerMsg> for ServerMsg {
|
||||||
|
fn from(value: NetServerMsg) -> Self {
|
||||||
|
Self::SendMsg(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn install_crypto_provider() {
|
pub fn install_crypto_provider() {
|
||||||
quinn::rustls::crypto::ring::default_provider()
|
quinn::rustls::crypto::ring::default_provider()
|
||||||
.install_default()
|
.install_default()
|
||||||
|
|||||||
@@ -6,9 +6,13 @@ use tokio::io::{AsyncReadExt as _, AsyncWriteExt};
|
|||||||
use tracing::Instrument as _;
|
use tracing::Instrument as _;
|
||||||
|
|
||||||
pub trait RecvHandler<M>: Send + Sync + 'static {
|
pub trait RecvHandler<M>: Send + Sync + 'static {
|
||||||
|
fn connect(&self) -> impl Future<Output = ()> + Send {
|
||||||
|
async {}
|
||||||
|
}
|
||||||
fn msg(&self, msg: M) -> impl Future<Output = ()> + Send;
|
fn msg(&self, msg: M) -> impl Future<Output = ()> + Send;
|
||||||
|
#[allow(unused)]
|
||||||
fn disconnect(&self, reason: DisconnectReason) -> impl Future<Output = ()> + Send {
|
fn disconnect(&self, reason: DisconnectReason) -> impl Future<Output = ()> + Send {
|
||||||
async { drop(reason) }
|
async {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,6 +43,7 @@ pub async fn recv_uni<M: bincode::Decode<()>>(
|
|||||||
Err(quinn::ConnectionError::ApplicationClosed { .. }) => {
|
Err(quinn::ConnectionError::ApplicationClosed { .. }) => {
|
||||||
return DisconnectReason::Closed;
|
return DisconnectReason::Closed;
|
||||||
}
|
}
|
||||||
|
Err(quinn::ConnectionError::LocallyClosed) => return DisconnectReason::Closed,
|
||||||
Err(quinn::ConnectionError::TimedOut) => {
|
Err(quinn::ConnectionError::TimedOut) => {
|
||||||
return DisconnectReason::Timeout;
|
return DisconnectReason::Timeout;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use directories_next::ProjectDirs;
|
|||||||
|
|
||||||
use crate::net::BINCODE_CONFIG;
|
use crate::net::BINCODE_CONFIG;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct DataDir {
|
pub struct DataDir {
|
||||||
dirs: ProjectDirs,
|
dirs: ProjectDirs,
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user