Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement TINPUT/ONEINPUT #9

Merged
merged 6 commits into from
Oct 6, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Implement timeout
  • Loading branch information
Riey committed Oct 6, 2022
commit 1deaba758eca43d1b98fab06bded8708a3deebd3
6 changes: 6 additions & 0 deletions erars-ast/src/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 13,12 @@ pub enum Value {
String(String),
}

impl Default for Value {
fn default() -> Self {
Value::Int(0)
}
}

impl Value {
pub const ZERO: Value = Value::Int(0);
pub const ONE: Value = Value::Int(1);
Expand Down
51 changes: 46 additions & 5 deletions src/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 7,11 @@ use regex::Regex;
use serde::{Deserialize, Serialize};
use smol_str::SmolStr;
use std::collections::VecDeque;
use std::sync::atomic::AtomicU32;
use std::sync::atomic::{AtomicBool, Ordering::SeqCst};
use std::sync::Arc;
use std::time::Duration;
use tokio::time::Instant;

#[cfg(feature = "stdio-backend")]
mod stdio_backend;
Expand Down Expand Up @@ -130,9 132,10 @@ impl ConsoleLine {
}

/// Used by ui backend
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Clone, Debug, PartialEq, Eq)]
struct VirtualConsole {
pub current_req: Option<InputRequest>,
timeout: Option<(Instant, u32, Value)>,
lines: Vec<ConsoleLine>,
style: TextStyle,
bg_color: Color,
Expand All @@ -143,6 146,7 @@ impl VirtualConsole {
fn new() -> Self {
Self {
current_req: None,
timeout: None,
lines: Vec::new(),
style: TextStyle {
color: Color([255, 255, 255]),
Expand All @@ -167,6 171,13 @@ impl VirtualConsole {
if self.current_req.is_some() {
log::warn!("Input overwrited");
}
if let Some(timeout) = req.timeout.as_ref() {
self.timeout = Some((
Instant::now() Duration::from_millis(timeout.timeout as _),
req.generation,
timeout.default_value.clone(),
));
}
self.current_req = Some(req);
}
ConsoleMessage::Alignment(align) => self.lines.last_mut().unwrap().align = align,
Expand Down Expand Up @@ -261,12 272,16 @@ pub enum InputRequestType {
/// input timeout
pub struct Timeout {
pub timeout: u32,
#[serde(skip)]
pub default_value: Value,
pub timeout_msg: Option<String>,
pub show_timer: bool,
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct InputRequest {
/// InputRequest generation
pub generation: u32,
/// type of request
pub ty: InputRequestType,
/// whether is ONEINPUT or not
Expand All @@ -277,16 292,18 @@ pub struct InputRequest {
}

impl InputRequest {
pub fn normal(ty: InputRequestType) -> Self {
pub fn normal(gen: u32, ty: InputRequestType) -> Self {
Self {
generation: gen,
ty,
is_one: false,
timeout: None,
}
}

pub fn oneinput(ty: InputRequestType) -> Self {
pub fn oneinput(gen: u32, ty: InputRequestType) -> Self {
Self {
generation: gen,
ty,
is_one: true,
timeout: None,
Expand Down Expand Up @@ -356,6 373,7 @@ impl ConsoleSender {
}
} else {
self.chan.send_msg(ConsoleMessage::Input(InputRequest {
generation: self.chan.input_gen(),
ty: InputRequestType::Int,
is_one: false,
timeout: None,
Expand All @@ -371,6 389,10 @@ impl ConsoleSender {
}
}

pub fn input_gen(&self) -> u32 {
self.chan.input_gen()
}

pub fn input(&mut self, req: InputRequest) -> ConsoleResult {
self.request_redraw();

Expand Down Expand Up @@ -521,6 543,7 @@ pub struct ConsoleChannel {
exit_fn: Mutex<Option<Box<dyn Fn() Send Sync>>>,
delay_redraw: AtomicBool,
delay_exit: AtomicBool,
input_generation: AtomicU32,
console: (Sender<ConsoleMessage>, Receiver<ConsoleMessage>),
ret: (Sender<ConsoleResult>, Receiver<ConsoleResult>),
}
Expand All @@ -532,11 555,16 @@ impl ConsoleChannel {
exit_fn: Mutex::new(None),
delay_redraw: AtomicBool::new(false),
delay_exit: AtomicBool::new(false),
input_generation: AtomicU32::new(0),
console: bounded(256),
ret: bounded(8),
}
}

pub fn input_gen(&self) -> u32 {
self.input_generation.load(SeqCst)
}

pub fn set_redraw_fn(&self, f: impl Fn() Send Sync 'static) {
let mut redraw_fn = self.redraw_fn.lock();

Expand Down Expand Up @@ -587,8 615,21 @@ impl ConsoleChannel {
self.console.1.recv_timeout(Duration::from_millis(50)).ok()
}

pub fn send_ret(&self, ret: ConsoleResult) {
self.ret.0.send(ret).unwrap()
pub fn send_quit(&self) {
self.ret.0.send(ConsoleResult::Quit).unwrap()
}

pub fn send_input(&self, input: Value, gen: u32) -> bool {
if self
.input_generation
.compare_exchange(gen, gen.wrapping_add(1), SeqCst, SeqCst)
.is_ok()
{
self.ret.0.send(ConsoleResult::Value(input)).unwrap();
true
} else {
false
}
}

pub fn recv_ret(&self) -> ConsoleResult {
Expand Down
83 changes: 53 additions & 30 deletions src/ui/http_backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 24,7 @@ use axum::{
use tower_http::compression::CompressionLayer;
use tower_http::cors;

use crate::ui::{Color, ConsoleLine, ConsoleResult, InputRequest, InputRequestType};
use crate::ui::{Color, ConsoleLine, InputRequest, InputRequestType};

use super::{EraApp, VirtualConsole};

Expand Down Expand Up @@ -102,28 102,30 @@ async fn start(
current_req = vconsole.current_req
);

match vconsole.current_req.as_ref().map(|i| i.ty) {
Some(InputRequestType::AnyKey | InputRequestType::EnterKey) => {
chan.send_ret(ConsoleResult::Value(Value::Int(0)));
vconsole.current_req = None;
StatusCode::OK
}
Some(InputRequestType::Int) => match request.trim().parse::<i64>() {
Ok(i) => {
chan.send_ret(ConsoleResult::Value(Value::Int(i)));
match vconsole.current_req.as_ref() {
Some(req) => match req.ty {
InputRequestType::AnyKey | InputRequestType::EnterKey => {
chan.send_input(Value::Int(0), req.generation);
vconsole.current_req = None;
StatusCode::OK
}
_ => {
log::error!("{request} is not Int");
StatusCode::BAD_REQUEST
InputRequestType::Int => match request.trim().parse::<i64>() {
Ok(i) => {
chan.send_input(Value::Int(i), req.generation);
vconsole.current_req = None;
StatusCode::OK
}
_ => {
log::error!("{request} is not Int");
StatusCode::BAD_REQUEST
}
},
InputRequestType::Str => {
chan.send_input(Value::String(request), req.generation);
vconsole.current_req = None;
StatusCode::OK
}
},
Some(InputRequestType::Str) => {
chan.send_ret(ConsoleResult::Value(Value::String(request)));
vconsole.current_req = None;
StatusCode::OK
}
None => StatusCode::GONE,
}
}),
Expand Down Expand Up @@ -169,21 171,25 @@ impl EraApp for HttpBackend {
rt.block_on(async move {
while !end.load(SeqCst) {
if need_redraw.swap(false, SeqCst) {
let mut vconsole = vconsole.write();
let mut vconsole_ = vconsole.write();
while let Some(msg) = chan.recv_msg() {
vconsole.push_msg(msg);
}
drop(vconsole);
let mut clients = clients.lock().await;
let mut invalid_clients = Vec::new();
for (_, (idx, client)) in clients.iter_mut() {
if client.send(Message::Binary(vec![1])).await.is_err() {
invalid_clients.push(*idx);
}
vconsole_.push_msg(msg);
}
for invalid_idx in invalid_clients {
clients.remove(invalid_idx);
if let Some((timeout, gen, default_value)) = vconsole_.timeout.take() {
let chan = chan.clone();
let clients = clients.clone();
tokio::spawn(async move {
tokio::time::sleep_until(timeout).await;
if chan.send_input(default_value, gen) {
log::debug!("Timeout {gen}");
let mut clients = clients.lock().await;
send_code(event_codes::TIMEOUT, &mut clients).await;
}
});
}
drop(vconsole_);
let mut clients = clients.lock().await;
send_code(event_codes::REDRAW, &mut clients).await;
tokio::time::sleep(Duration::from_millis(100)).await;
continue;
}
Expand All @@ -196,3 202,20 @@ impl EraApp for HttpBackend {
Ok(())
}
}

async fn send_code(code: u8, clients: &mut Slab<(usize, WebSocket)>) {
let mut invalid_clients = Vec::new();
for (_, (idx, client)) in clients.iter_mut() {
if client.send(Message::Binary(vec![code])).await.is_err() {
invalid_clients.push(*idx);
}
}
for invalid_idx in invalid_clients {
clients.remove(invalid_idx);
}
}

mod event_codes {
pub const REDRAW: u8 = 1;
pub const TIMEOUT: u8 = 2;
}
15 changes: 6 additions & 9 deletions src/ui/stdio_backend.rs
Original file line number Diff line number Diff line change
@@ -1,7 1,4 @@
use super::{
ConsoleChannel, ConsoleLinePart, ConsoleResult, EraApp, FontStyle, InputRequestType,
VirtualConsole,
};
use super::{ConsoleChannel, ConsoleLinePart, EraApp, FontStyle, InputRequestType, VirtualConsole};
use std::{
io::{self, BufRead},
sync::{
Expand Down Expand Up @@ -85,25 82,25 @@ impl EraApp for StdioBackend {

self.draw(&mut lock)?;

if let Some(ty) = self.vconsole.current_req.as_ref().map(|i| i.ty) {
if let Some(req) = self.vconsole.current_req.as_ref() {
let size = stdin.read_line(&mut input)?;

let s = input[..size].trim_end_matches(&['\r', '\n']);

match ty {
match req.ty {
InputRequestType::Int => match s.trim().parse() {
Ok(i) => {
chan.send_ret(ConsoleResult::Value(Value::Int(i)));
chan.send_input(Value::Int(i), req.generation);
self.vconsole.current_req = None;
}
Err(_) => {}
},
InputRequestType::Str => {
chan.send_ret(ConsoleResult::Value(Value::String(s.into())));
chan.send_input(Value::String(s.into()), req.generation);
self.vconsole.current_req = None;
}
InputRequestType::AnyKey | InputRequestType::EnterKey => {
chan.send_ret(ConsoleResult::Value(Value::Int(0)));
chan.send_input(Value::Int(0), req.generation);
self.vconsole.current_req = None;
}
}
Expand Down
Loading