├── .gitignore ├── src ├── lua.rs ├── messages.rs ├── main.rs ├── participant.rs ├── ui.rs └── host.rs ├── .idea ├── encodings.xml ├── codeStyles │ ├── codeStyleConfig.xml │ └── Project.xml ├── vcs.xml ├── misc.xml ├── modules.xml ├── inspectionProfiles │ └── Project_Default.xml └── workspace.xml ├── Cargo.toml ├── midas.iml ├── LICENCE ├── docs └── sample_code.lua ├── readme.md ├── changelog.md └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /src/lua.rs: -------------------------------------------------------------------------------- 1 | 2 | pub type SerdeLuaTable = Vec<(hlua::AnyLuaValue, hlua::AnyLuaValue)>; 3 | 4 | 5 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "midas" 3 | version = "0.2.18" 4 | authors = ["ray33ee <30669752+ray33ee@users.noreply.github.com>"] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | 11 | message-io = "0.6.0" 12 | serde = { version = "1.0.123", features = ["derive"] } 13 | clap = "2.33.3" 14 | ipaddress = "0.1.1" 15 | hlua = "0.4.1" 16 | bimap = "0.6.0" 17 | crossterm = "0.19.0" 18 | crossbeam-channel = "0.5.0" 19 | crossbeam = "0.8.0" 20 | tui = { version = "0.14", default-features = false, features = ['crossterm'] } 21 | chrono = "0.4.19" 22 | num_cpus = "1.14.0" 23 | 24 | [patch.crates-io] 25 | hlua = { git = "https://github.com/ray33ee/hlua" } 26 | -------------------------------------------------------------------------------- /midas.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [year] [fullname] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /docs/sample_code.lua: -------------------------------------------------------------------------------- 1 | 2 | prime = 96174894 3 | 4 | function generate_data(endpoint_index, endpoint_count) 5 | 6 | if endpoint_index == 0 then _print("Script Version 0.1.1") end 7 | 8 | data = {} 9 | 10 | 11 | upper = prime --math.floor(math.sqrt(prime)) 12 | width = math.floor((upper - 2) / endpoint_count) 13 | 14 | data.lower = width * endpoint_index + 2 15 | data.upper = data.lower + width - 1 16 | 17 | 18 | return data 19 | 20 | end 21 | 22 | function execute_code() 23 | 24 | _print("Message from script") 25 | 26 | participant_result = {} 27 | 28 | participant_result.lower = global_data.lower 29 | participant_result.upper = global_data.upper 30 | 31 | lower = global_data.lower 32 | upper = global_data.upper 33 | 34 | 35 | for i = lower,upper,1 36 | do 37 | if i % 100000 == 0 then _check() end 38 | 39 | if i % 100000 == 0 then _progress((i - lower) / (upper - lower) * 100, 1000) end 40 | 41 | if (prime % i == 0 ) 42 | then 43 | participant_result.divisor = i 44 | 45 | end 46 | 47 | end 48 | 49 | participant_result.divisor = 0 50 | 51 | 52 | return participant_result 53 | 54 | end 55 | 56 | function interpret_results() 57 | 58 | 59 | for i,v in pairs(results) 60 | do 61 | if (v.divisor ~= 0) 62 | then 63 | return "The number is divisible by ".. v.divisor 64 | end 65 | 66 | end 67 | return "The number is prime." 68 | end -------------------------------------------------------------------------------- /src/messages.rs: -------------------------------------------------------------------------------- 1 | 2 | use serde::{Serialize, Deserialize}; 3 | use message_io::network::Endpoint; 4 | use message_io::network::NetEvent; 5 | 6 | use crate::lua::SerdeLuaTable; 7 | use tui::style::{Style, Color, Modifier}; 8 | use tui::widgets::Cell; 9 | 10 | #[derive(Serialize, Deserialize, Debug)] 11 | pub enum Message { 12 | /* Host to participant */ 13 | 14 | Code(String), 15 | VectorHTP(SerdeLuaTable), 16 | Execute, 17 | 18 | Play, 19 | Pause, 20 | Stop, 21 | Kill, 22 | 23 | /* Participant to Host */ 24 | 25 | VectorPTH(SerdeLuaTable), 26 | 27 | Progress(f32), 28 | 29 | ParticipantError(String), 30 | ParticipantWarning(String), 31 | Whisper(String), 32 | Stdout(String), 33 | 34 | Paused, 35 | Continued, 36 | Executing, 37 | 38 | Register(String), 39 | Unregister 40 | } 41 | 42 | #[derive(PartialEq, Eq, Hash, Clone, Debug)] 43 | pub enum ParticipantStatus { 44 | Idle, 45 | Calculating, 46 | Paused, 47 | 48 | } 49 | 50 | impl ParticipantStatus { 51 | pub fn to_color(& self) -> Color { 52 | match self { 53 | ParticipantStatus::Idle => Color::Green, 54 | ParticipantStatus::Calculating => Color::Rgb(255, 255, 0), 55 | ParticipantStatus::Paused => Color::Rgb(255, 128, 0), 56 | } 57 | } 58 | } 59 | 60 | #[derive(PartialEq, Eq, Hash)] 61 | pub enum Severity { 62 | Info, 63 | Warning, 64 | Error, 65 | Result, 66 | Stdout, 67 | Starting 68 | } 69 | 70 | impl Severity { 71 | pub fn to_cell(& self) -> Cell { 72 | match self { 73 | 74 | Severity::Starting => Cell::from("STARTING").style(Style::default().fg(Color::Rgb(108, 186, 133))), 75 | Severity::Error => Cell::from("ERROR").style(Style::default().fg(Color::Rgb(212, 65, 67))), 76 | Severity::Warning => Cell::from("WARNING").style(Style::default().fg(Color::Rgb(207, 114, 65))), 77 | Severity::Result => Cell::from("RESULT").style(Style::default().fg(Color::Rgb(221, 183, 45))), 78 | Severity::Stdout => Cell::from("STDOUT").style(Style::default().fg(Color::Rgb(178, 214, 90))), 79 | Severity::Info => Cell::from("INFO").style(Style::default()), 80 | } 81 | } 82 | } 83 | 84 | pub enum NodeType { 85 | Host, 86 | Participant(String), 87 | } 88 | 89 | impl NodeType { 90 | pub fn to_cell(& self) -> Cell { 91 | match self { 92 | NodeType::Host => Cell::from("Host").style(Style::default().fg(Color::Rgb(37, 158, 175))), 93 | NodeType::Participant(name) => Cell::from(name.as_str()).style(Style::default().fg(Color::Rgb(127, 82, 198)).add_modifier(Modifier::ITALIC)), 94 | } 95 | } 96 | } 97 | 98 | pub enum UiEvents { 99 | ChangeStatusTo(ParticipantStatus, Endpoint, String), 100 | 101 | ParticipantProgress(String, f32), 102 | 103 | Log(NodeType, String, Severity), 104 | 105 | ParticipantRegistered(Endpoint, String), 106 | ParticipantUnregistered(String), 107 | 108 | InterpretResultsReturn(String), 109 | 110 | } 111 | 112 | 113 | pub enum HostEvent { 114 | Network(NetEvent), 115 | Pause(Endpoint), 116 | Play(Endpoint), 117 | Kill(Endpoint), 118 | 119 | Begin(String), 120 | 121 | RemoveAll, 122 | 123 | PlayAll, 124 | PauseAll, 125 | KillAll, 126 | } -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Midas 2 | Midas is a distributed computing system written entirely in Rust. 3 | 4 | By running Midas on a host device then assigning participants we can create a distributed computing network using Lua. Messages between host and 5 | participant are passed using [message-io](https://docs.rs/message-io/0.8.1/message_io/) and code is executed using [hlua](https://docs.rs/hlua/0.4.1/hlua/). 6 | These two combined allow the host to send code to participants for them to execute. 7 | 8 | ![Screenshot](https://i.imgur.com/h5LnB3d.png) 9 | 10 | ## Why? 11 | 12 | There is no shortage of distributed computing models, and each model has many implementations. If power and performance is 13 | required these solutions are undoubtedly the best, especially for performing one task, and doing it well. 14 | 15 | However, these solutions are extremely thorough and therefore have a steep learning curve. For general experimenting Midas is 16 | perfect since it is easy to learn (only knowledge of Lua and this readme is required) and it easy to swap out algorithms, 17 | rather than being having to stick with a single executable. 18 | 19 | ## Host setup 20 | 21 | Creating a host can be done by specifying an IP address (with port number) and the script to execute: 22 | 23 | ```shell 24 | midas --address=127.0.0.1:3000 host --script"C:\script.lua" 25 | ``` 26 | 27 | ## Participant setup 28 | 29 | Creating a participant is similar, we must use the address we specified for the host (in this case 127.0.0.1:3000) and this time a unique name for the participant. 30 | 31 | ```shell 32 | midas --address=127.0.0.1:3000 participant --name="laptop" 33 | ``` 34 | 35 | A name must be supplied to identify the participants in the host. If the number of threads is omitted, we automatically determine the number of threads to use. 36 | 37 | ## Lua scripts 38 | 39 | The Lua scripts are executed by the host and participants, not only to execute the parallel code, but also to load the input data and process the output data. The script must implement the three following functions 40 | 41 | A single command may create multiple participants, this is because we try to create as many participants as the computer can handle concurrently. This can be controlled with the threads command line option. 42 | 43 | ### `generate_data` 44 | 45 | This function is called by the host for each participant and should be used to generate the input data for participants. It takes two integers as arguments, the index of the participant, and the number of participants registered, these can be used to split the data up. 46 | 47 | The `generate_data` function can be used to algorithmically generate data, or load data from a file on the host. 48 | 49 | The return value is a table which is sent to the participant 50 | 51 | Midas provides two extra functions that can be used to communicate extra information to the host, at the expense of increased overhead. 52 | Using these functions is not mandatory, so for performance intensive calculations these can be ignored. 53 | 54 | #### `_check` 55 | 56 | Detects and handles pause/play/stop events sent by the host. 57 | For example, if a main loop is used within `generate_data` then calling `_check` occasionally within this loop will allow users to pause, play and stop the execution. 58 | 59 | Note: The `_check` function carries some overhead, so calling it every iteration of a loop is highly discouraged. 60 | 61 | #### `_progress` 62 | 63 | Sends a percentage (as `f32`) to the host to indicate the progress through the execution. 64 | 65 | It also takes a `u32`, which is the max duration (in milliseconds) between progress updates. It helps prevent the code from sending too many progress updates too frequently and slowing down execution. 66 | 67 | Even with the duration restriction, calling `_progress` still incurs some overhead, so should not be called too frequently. 68 | 69 | #### `_print` 70 | 71 | Accepts a string, used to print custom messages which will be displayed on the host. Using Lua's `print` will print to the participant and will NOT print to host. 72 | 73 | ### `execute_code` 74 | 75 | The `execute_code` function is called by each participant and takes no arguments, but it does have access to a global variable, `global_data` which is simply the table returned by the `generate_data`. 76 | 77 | While no arguments are accepted by the function, any data can be sent to the function by including it in the `generate_data` step. 78 | 79 | This function also returns a table and sends it back to the host on completion. 80 | 81 | ### `interpret_results` 82 | 83 | This function is used to take the data from the `execute_code` calls, collects them and processes it. It also takes no arguments, and exposes another global variable `results` which is an array of tables, one for each table from each participant returned by `execute_code`. 84 | 85 | This functions returns a string, which can be used to show a message indicating the result of the processing, or show an error message. 86 | 87 | ## Build 88 | 89 | To build, simply download and unzip the [repo](https://github.com/ray33ee/Project-Midas/archive/master.zip), navigate to the unzipped repo and execute the following command 90 | 91 | ```shell 92 | cargo build --release 93 | ``` 94 | 95 | Then navigate to the `target/release` folder and execute `midas` with the command options as stated above 96 | 97 | ## Native binaries 98 | 99 | Alternatively you can find compiled binaries for midas [here](https://sourceforge.net/projects/project-midas/) 100 | 101 | ## Host longevity 102 | 103 | Once a task is started, the host application must run at least until the partcipants have all stopped, it may not stop earlier. If it does, all participants will stop immediately. It is also important to mention that a node can host as well as participate by using different processes for the host. 104 | This means that a dedicated Host node is not needed, and the host code can be run on any of the nodes. 105 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | mod host; 2 | mod participant; 3 | mod messages; 4 | mod lua; 5 | mod ui; 6 | 7 | extern crate clap; 8 | extern crate serde; 9 | 10 | use clap::{crate_version, Arg, App, SubCommand}; 11 | use std::thread; 12 | 13 | use crate::ui::Panel; 14 | use crate::host::Host; 15 | 16 | use crate::messages::{HostEvent, UiEvents}; 17 | use crossbeam_channel::unbounded; 18 | use message_io::network::{Network, Transport, NetEvent}; 19 | 20 | fn main() { 21 | 22 | 23 | 24 | use std::net::SocketAddrV4; 25 | use std::str::FromStr; 26 | 27 | 28 | 29 | let app_matches = App::new("Midas") 30 | .version(crate_version!()) 31 | .author("Will Cooper") 32 | .about("Distributed network based parallel computing system") 33 | .arg(Arg::with_name("socket address") 34 | .short("a") 35 | .long("address") 36 | .takes_value(true) 37 | .help("Socket address to host/connect to. Pleas specify Ip address and port number, such as '192.168.0.1:4000'.") 38 | .validator( |value| 39 | match SocketAddrV4::from_str(value.as_str()) { 40 | Ok(_) => Ok(()), 41 | Err(e) => Err(e.to_string()) 42 | } 43 | ) 44 | .required(true)) 45 | .subcommand(SubCommand::with_name("host") 46 | .arg(Arg::with_name("Lua script") 47 | .short("s") 48 | .long("script") 49 | .takes_value(true) 50 | .help("Lua script to run") 51 | .validator(|value| 52 | if std::path::Path::new(value.as_str()).exists() { 53 | Ok(()) 54 | } 55 | else { 56 | Err(format!("Lua script does not exist ({}).", value)) 57 | } 58 | ) 59 | .required(true)) 60 | .about("Executes Midas as the host")) 61 | .subcommand(SubCommand::with_name("participant") 62 | .arg(Arg::with_name("participant name") 63 | .short("n") 64 | .long("name") 65 | .help("Name of the participant, used by the host to identify participants") 66 | .takes_value(true) 67 | .required(true)) 68 | .arg(Arg::with_name("thread count") 69 | .short("t") 70 | .long("threads") 71 | .help("Number of threads. If no number is supplied, the value is calculated automatically.") 72 | .validator(|value| 73 | match value.parse::() { 74 | Ok(_) => Ok(()), 75 | Err(e) => Err(format!("Invalid number of threads: Could not convert {} to number because '{}'.", value, e)) 76 | } 77 | ) 78 | .takes_value(true) 79 | .required(false)) 80 | .about("Executes Midas as a participant")) 81 | .get_matches(); 82 | 83 | let ip_address = app_matches.value_of("socket address").unwrap(); 84 | 85 | //Setup the channels of communication between Host code and ui 86 | let (command_sender, command_receiver) = unbounded::(); 87 | let (message_sender, message_receiver) = unbounded::(); 88 | 89 | match app_matches.subcommand() { 90 | ("host", host_matches) => { 91 | 92 | match Host::new(command_receiver, command_sender.clone(), message_sender,ip_address) { 93 | Ok(mut host) => { 94 | let script_path = host_matches.unwrap().value_of("Lua script").unwrap(); 95 | 96 | let mut panel = Panel::new(command_sender.clone(), message_receiver, script_path); 97 | 98 | thread::spawn(move || 99 | loop { 100 | host.check_events() 101 | } 102 | ); 103 | 104 | while let Ok(_) = panel.tick() {} 105 | }, 106 | Err(error) => { 107 | println!("Host Error - {}", error); 108 | } 109 | } 110 | 111 | 112 | }, 113 | ("participant", participant_matches) => { 114 | 115 | 116 | let thread_count: usize = if participant_matches.unwrap().is_present("thread count") 117 | { 118 | let number = participant_matches.unwrap().value_of("thread count").unwrap(); 119 | number.parse::().unwrap() 120 | } 121 | else { 122 | num_cpus::get() 123 | }; 124 | 125 | let participant_name = participant_matches.unwrap().value_of("participant name").unwrap(); 126 | 127 | loop 128 | { 129 | println!("Searching for host..."); 130 | 131 | { 132 | let mut network = Network::new(move |_: NetEvent<()>| {}); 133 | 134 | 135 | while let Err(_) = network.connect(Transport::Tcp, ip_address) { 136 | 137 | } 138 | 139 | } 140 | 141 | println!("Found host!"); 142 | 143 | crossbeam::thread::scope(|s| { 144 | let participant_name = participant_name; 145 | let ip_address = ip_address; 146 | 147 | for i in 0..thread_count { 148 | s.builder() 149 | .name(format!("thread_{}-{}", &participant_name, i)) 150 | .spawn(move |_| { 151 | let mut participant = participant::Participant::new( 152 | if thread_count == 1 { 153 | format!("{}", participant_name) 154 | } else { 155 | format!("{}-{:03}", participant_name, i) 156 | }, ip_address).unwrap(); 157 | 158 | while let Ok(_) = participant.tick() {} 159 | }).unwrap(); 160 | } 161 | }).unwrap(); 162 | 163 | println!("Disconnected."); 164 | 165 | 166 | } 167 | 168 | }, 169 | _ => unreachable!() 170 | }; 171 | } 172 | -------------------------------------------------------------------------------- /src/participant.rs: -------------------------------------------------------------------------------- 1 | use hlua::{AnyLuaValue, Lua, LuaTable}; 2 | use message_io::network::{NetEvent, Network, Transport}; 3 | 4 | use crate::messages::Message; 5 | 6 | use crossbeam_channel::{Sender, Receiver, unbounded, RecvTimeoutError}; 7 | 8 | use std::thread; 9 | use std::time::Duration; 10 | 11 | pub struct Participant<'a> { 12 | 13 | network: Sender, 14 | 15 | //message_sender: Sender>, 16 | message_receiver: Receiver>, 17 | 18 | lua: Lua<'a>, 19 | } 20 | 21 | impl<'a> Drop for Participant<'a> { 22 | fn drop(&mut self) { 23 | 24 | } 25 | } 26 | 27 | impl<'a> Participant<'a> { 28 | 29 | pub fn new(name: String, server_address: &str) -> Result { 30 | 31 | let (message_sender, message_receiver) = unbounded(); 32 | 33 | let mut lua = Lua::new(); 34 | 35 | lua.openlibs(); 36 | 37 | let (net_sender, net_receiver) = unbounded(); 38 | 39 | let network_sender = message_sender.clone(); 40 | 41 | let mut network = Network::new(move |net_event| network_sender.send(net_event).unwrap()); 42 | 43 | match network.connect(Transport::Tcp, server_address) { 44 | Ok(host_endpoint) => { 45 | println!("Participant '{}' connected to host ({})", name, server_address); 46 | 47 | // The following thread monitors the net_sender/net_receiver channel and sends any data 48 | // it receives accross the network. This allows us to have multiple senders to the network 49 | thread::spawn(move || 50 | { 51 | 52 | 53 | loop { 54 | match net_receiver.recv() { 55 | Ok(message) => { 56 | network.send(host_endpoint, message); 57 | } 58 | Err(_e) => { 59 | break; 60 | } 61 | } 62 | } 63 | } 64 | ); 65 | 66 | // Register the participant 67 | net_sender.send(Message::Register(String::from(name))).unwrap(); 68 | 69 | Ok(Participant { 70 | network: net_sender, 71 | message_receiver, 72 | lua 73 | }) 74 | } 75 | Err(_e) => { 76 | Err(()) 77 | //panic!("Could not connect to {} - {}", server_address, e); 78 | } 79 | } 80 | 81 | 82 | } 83 | 84 | fn recv_message(rec: & Receiver>, dur: Option) -> Option { 85 | let net_event = if let Some(duration) = dur { 86 | match rec.recv_timeout(Duration::from_micros(duration)) { 87 | Ok(msg) => { 88 | msg 89 | } 90 | Err(receive_error) => match receive_error { 91 | RecvTimeoutError::Disconnected => { 92 | panic!("Participant::message_receiver has disconnected") 93 | }, 94 | RecvTimeoutError::Timeout => { 95 | return None; 96 | } 97 | } 98 | } 99 | } else { 100 | rec.recv().expect("Participant::message_receiver has disconnected") 101 | }; 102 | 103 | match net_event { 104 | NetEvent::Message(_, message) => { 105 | Some(message) 106 | }, 107 | NetEvent::RemovedEndpoint(_endpoint) => { 108 | println!("Server Disconnected. See Host for more details."); 109 | std::process::exit(0); 110 | } 111 | _ => { 112 | None 113 | } 114 | } 115 | } 116 | 117 | pub fn tick(& mut self) -> Result<(), ()> { 118 | 119 | 120 | match self.message_receiver.recv() { 121 | Ok(nevent) => match nevent { 122 | NetEvent::Message(_, message) => { 123 | match message { 124 | Message::Code(code) => { 125 | 126 | 127 | 128 | let net_sender = self.network.clone(); 129 | 130 | self.lua.set("_print", hlua::function1(move |message: String| { 131 | net_sender.send(Message::Stdout(message)).unwrap(); 132 | })); 133 | 134 | //Register the _check function which allows Lua script users to check the 135 | //network and respond to pause/play and stop commands 136 | let receiver = self.message_receiver.clone(); 137 | let net_sender = self.network.clone(); 138 | 139 | 140 | 141 | self.lua.set("_check", hlua::function0(move || 142 | { 143 | let refy = & receiver; 144 | 145 | 146 | //println!("Check start"); 147 | match Self::recv_message(refy, Some(0)) { 148 | Some(msg) => match msg { 149 | Message::Kill => { 150 | std::process::exit(0); 151 | //panic!("This is a cheaty way to kill the thread, but fuck it, we'll do it live!"); 152 | } 153 | Message::Pause => { 154 | 155 | net_sender.send(Message::Paused).unwrap(); 156 | loop { 157 | match Self::recv_message(refy, None) { 158 | Some(ms) => match ms { 159 | Message::Kill => { 160 | std::process::exit(0); 161 | //panic!("This is a cheaty way to kill the thread, but fuck it, we'll do it live!"); 162 | } 163 | Message::Play => { 164 | 165 | net_sender.send(Message::Executing).unwrap(); 166 | break; 167 | } 168 | _ => { 169 | println!("Inner: {:?}", msg); 170 | } 171 | } 172 | None => { 173 | 174 | } 175 | } 176 | } 177 | 178 | } 179 | _ => { 180 | println!("MESSAGE RECEIVED DURING PAUSE Check: {:?}", msg); 181 | } 182 | } 183 | None => { 184 | 185 | } 186 | } 187 | //println!("Check finish"); 188 | } 189 | )); 190 | 191 | //Register the _progress function which allows Lua script users to send 192 | //data back to the host indicating how much progress the script has made 193 | let net_sender = self.network.clone(); 194 | 195 | let mut last_progress_update = std::time::Instant::now(); 196 | 197 | self.lua.set("_progress", hlua::function2(move |prog: f32, delay: u32| 198 | { 199 | if std::time::Instant::now().duration_since(last_progress_update).as_millis() > delay as u128 { 200 | net_sender.send(Message::Progress(prog)).unwrap(); 201 | last_progress_update = std::time::Instant::now(); 202 | } 203 | 204 | })); 205 | 206 | 207 | match self.lua.execute::<()>(code.as_str()) { 208 | Ok(_) => {} 209 | Err(e) => { 210 | self.network.send( Message::ParticipantError(String::from(format!("LuaError on receive Message::Code - {:?}", e)))).unwrap(); 211 | panic!("LuaError on receive Message::Code - {:?}", e); 212 | } 213 | } 214 | }, 215 | Message::VectorHTP(data) => { 216 | let mut arr = self.lua.empty_array("global_data"); 217 | 218 | 219 | for (_, value) in data.iter().enumerate() { 220 | arr.set(value.0.clone(), value.1.clone()); 221 | } 222 | }, 223 | Message::Pause => {}, 224 | Message::Play => {}, 225 | Message::Stop => {}, 226 | Message::Kill => { 227 | std::process::exit(0); 228 | //panic!("This is a cheaty way to kill the thread, but fuck it, we'll do it live!"); 229 | }, 230 | Message::Execute => { 231 | 232 | match self.lua.get::, _>("execute_code") 233 | { 234 | Some(mut generate_data) => { 235 | 236 | self.network.send(Message::Executing).unwrap(); 237 | 238 | match generate_data.call::>() { 239 | Ok(mut result) => { 240 | let list: crate::lua::SerdeLuaTable = result.iter::().map(|pair| pair.unwrap()).collect(); 241 | 242 | self.network.send( Message::VectorPTH(list)).unwrap(); 243 | //result 244 | } 245 | Err(e) => { 246 | self.network.send(Message::ParticipantError(String::from(format!("LuaError on receive Message::Execute (Lua function return type) - {:?}", e)))).unwrap(); 247 | panic!("LuaError on receive Message::Execute - {:?}", e); 248 | } 249 | }; 250 | 251 | 252 | 253 | }, 254 | None => { 255 | self.network.send( Message::ParticipantError(String::from("LuaError on receive Message::Execute (Lua function call) - Function 'execute_code' does not exist."))).unwrap(); 256 | panic!("LuaError on receive Message::Execute - Function 'execute_code' does not exist."); 257 | } 258 | } 259 | }, 260 | 261 | _ => { 262 | self.network.send(Message::ParticipantError(format!("Invalid message {:?}", message))).unwrap(); 263 | panic!("Invalid message {:?}", message); 264 | } 265 | } 266 | } 267 | NetEvent::AddedEndpoint(_endpoint) => {}, 268 | NetEvent::RemovedEndpoint(_endpoint) => { 269 | println!("Server Disconnected. See Host for more details."); 270 | return Err(()) 271 | } 272 | NetEvent::DeserializationError(_) => (), 273 | 274 | } 275 | Err(_) => { 276 | 277 | } 278 | } 279 | 280 | Ok(()) 281 | } 282 | 283 | } 284 | -------------------------------------------------------------------------------- /src/ui.rs: -------------------------------------------------------------------------------- 1 | 2 | use crate::messages::{UiEvents, HostEvent, ParticipantStatus, Severity, NodeType}; 3 | use std::time::Duration; 4 | 5 | use crossbeam_channel::{Sender, Receiver}; 6 | 7 | use crossterm::event::{read, Event, poll}; 8 | 9 | 10 | use std::io; 11 | use tui::Terminal; 12 | use tui::backend::CrosstermBackend; 13 | use tui::widgets::{Block, Borders, ListItem, List, Row, Table, Cell, ListState, Paragraph}; 14 | use tui::layout::{Layout, Constraint, Direction}; 15 | use tui::style::{Color, Style}; 16 | use tui::text::{Spans, Span}; 17 | 18 | 19 | use std::io::Stdout; 20 | use bimap::BiMap; 21 | use message_io::network::Endpoint; 22 | 23 | use chrono::{Utc, DateTime}; 24 | use tui::text::Text; 25 | 26 | #[derive(PartialEq, Eq, Hash, Clone)] 27 | struct ParticipantInfo { 28 | endpoint: Endpoint, 29 | status: ParticipantStatus, 30 | progress: Option, 31 | } 32 | 33 | impl ParticipantInfo { 34 | fn new(endpoint: Endpoint) -> Self { 35 | 36 | ParticipantInfo { 37 | endpoint, 38 | status: ParticipantStatus::Idle, 39 | progress: None, 40 | } 41 | } 42 | } 43 | 44 | struct LogEntry { 45 | severity: Severity, 46 | node_type: NodeType, 47 | message: String, 48 | time: DateTime, 49 | } 50 | 51 | impl LogEntry { 52 | fn new(severity: Severity, node_type: NodeType, message: String) -> Self { 53 | LogEntry { 54 | time: Utc::now(), 55 | severity, 56 | node_type, 57 | message 58 | } 59 | } 60 | 61 | fn to_listitem(& self) -> Row { 62 | 63 | Row::new(vec![ 64 | Cell::from(format!("{}", self.time.format("%F %X"))), 65 | self.severity.to_cell(), 66 | self.node_type.to_cell(), 67 | Cell::from(self.message.as_str()), 68 | ]) 69 | } 70 | } 71 | 72 | 73 | 74 | pub struct Panel<'a> { 75 | command_sender: Sender, 76 | 77 | message_receiver: Receiver, 78 | 79 | terminal: Terminal>, 80 | 81 | script_path: & 'a str, 82 | 83 | participants: BiMap, 84 | 85 | participant_names: Vec, 86 | 87 | selected_participant: Option, 88 | 89 | participants_state: ListState, 90 | 91 | top_scroll_log_item: usize, //Index of the log item to show at the top 92 | 93 | 94 | 95 | logs: Vec, 96 | 97 | } 98 | 99 | impl<'a> Panel<'a> { 100 | pub fn new(command_sender: Sender, message_receiver: Receiver, script_path: & 'a str) -> Self { 101 | 102 | let stdout = io::stdout(); 103 | let backend = CrosstermBackend::new(stdout); 104 | let mut terminal = Terminal::new(backend).unwrap(); 105 | terminal.clear().unwrap(); 106 | 107 | 108 | Panel { 109 | command_sender, 110 | message_receiver, 111 | terminal, 112 | script_path, 113 | participants: BiMap::new(), 114 | participants_state: ListState::default(), 115 | selected_participant: None, 116 | participant_names: Vec::new(), 117 | top_scroll_log_item: 0, 118 | logs: Vec::new() 119 | } 120 | } 121 | 122 | fn is_calculating(& self) -> bool { 123 | let mut is_calculating = false; 124 | 125 | for info in self.participants.right_values().into_iter() { 126 | is_calculating |= info.status != ParticipantStatus::Idle; 127 | } 128 | 129 | is_calculating 130 | } 131 | 132 | pub fn tick(& mut self) -> Result<(), ()> { 133 | 134 | //If no participant is selected, try and select one 135 | if !self.participant_names.is_empty() && self.selected_participant == Option::None { 136 | self.selected_participant = Some(self.participant_names.get(0).unwrap().clone()); 137 | self.participants_state.select(Some(0)); 138 | } 139 | 140 | //When a button is clicked or an action is invoked, we must send the event via the ui_sender 141 | if let Ok(true) = poll(Duration::from_secs(0)) { 142 | match read().unwrap() { 143 | Event::Key(key_event) => { 144 | match key_event.code { 145 | crossterm::event::KeyCode::Char('e') => { 146 | 147 | if !self.is_calculating() { 148 | self.command_sender.send(HostEvent::Begin(String::from(self.script_path))).unwrap(); 149 | 150 | } 151 | 152 | }, 153 | crossterm::event::KeyCode::Char('p') => { 154 | self.command_sender.send(HostEvent::PauseAll).unwrap(); 155 | }, 156 | crossterm::event::KeyCode::Char('l') => { 157 | self.command_sender.send(HostEvent::PlayAll).unwrap(); 158 | }, 159 | crossterm::event::KeyCode::Char('k') => { 160 | self.command_sender.send(HostEvent::KillAll).unwrap(); 161 | self.logs.insert(0, LogEntry::new(Severity::Info, NodeType::Host, format!("Terminating all participants."))); 162 | 163 | }, 164 | crossterm::event::KeyCode::Char('q') => { 165 | 166 | 167 | self.command_sender.send(HostEvent::RemoveAll).unwrap(); 168 | self.terminal.clear().unwrap(); 169 | return Err(()); 170 | }, 171 | crossterm::event::KeyCode::Char('c') => { 172 | self.logs.clear(); 173 | self.top_scroll_log_item = 0; 174 | }, 175 | crossterm::event::KeyCode::Left => { 176 | 177 | 178 | }, 179 | crossterm::event::KeyCode::Right => { 180 | 181 | }, 182 | crossterm::event::KeyCode::Up => { 183 | 184 | if !self.participants.is_empty() { 185 | 186 | let selected_index = self.participants_state.selected().unwrap(); 187 | 188 | if selected_index != 0 { 189 | self.participants_state.select(Some(selected_index - 1)); 190 | } else { 191 | self.participants_state.select(Some(self.participant_names.len() - 1)); 192 | } 193 | } 194 | }, 195 | crossterm::event::KeyCode::Down => { 196 | if !self.participants.is_empty() { 197 | let selected_index = self.participants_state.selected().unwrap(); 198 | 199 | self.participants_state.select(Some(selected_index + 1)); 200 | } 201 | }, 202 | crossterm::event::KeyCode::PageUp => { 203 | if self.top_scroll_log_item < 10 { 204 | self.top_scroll_log_item = 0; 205 | } else { 206 | self.top_scroll_log_item -= 10; 207 | 208 | } 209 | 210 | 211 | }, 212 | crossterm::event::KeyCode::PageDown => { 213 | if self.top_scroll_log_item + 10 < self.logs.len() { 214 | self.top_scroll_log_item += 10; 215 | } 216 | 217 | }, 218 | _ => {} 219 | } 220 | } 221 | _ => {} 222 | } 223 | } 224 | 225 | self.selected_participant = match self.participants_state.selected() { 226 | Some(index) => match self.participant_names.get(index) { 227 | Some(name) => Some(name.clone()), 228 | None => None 229 | } 230 | None => None 231 | }; 232 | 233 | //We must also check ui_event_queue and see if we need to change the UI 234 | match self.message_receiver.recv_timeout(Duration::from_micros(0)) { 235 | Ok(event) => match event { 236 | UiEvents::ChangeStatusTo(status, _endpoint, name) => { 237 | let (_, mut info) = self.participants.remove_by_left(&name).unwrap(); 238 | if let ParticipantStatus::Idle = status { 239 | info.progress = None; 240 | } 241 | info.status = status; 242 | self.participants.insert(name, info); 243 | 244 | }, 245 | UiEvents::Log(node_type, message, severity) => { 246 | 247 | self.logs.insert(0, LogEntry::new(severity, node_type, message)); 248 | } 249 | 250 | UiEvents::ParticipantRegistered(endpoint, name) => { 251 | self.participants.insert(name, ParticipantInfo::new(endpoint)); 252 | }, 253 | UiEvents::ParticipantUnregistered( name) => { 254 | 255 | self.logs.insert(0, LogEntry::new(Severity::Warning, NodeType::Participant(name.clone()), format!("Participant has disconnected."))); 256 | self.participants.remove_by_left(&name); 257 | }, 258 | UiEvents::InterpretResultsReturn(return_message) => { 259 | self.logs.insert(0, LogEntry::new(Severity::Result, NodeType::Host, return_message)); 260 | }, 261 | UiEvents::ParticipantProgress(name, progress) => { 262 | let (_, mut info) = self.participants.remove_by_left(&name).unwrap(); 263 | info.progress = Some((progress * 100.0f32) as i32); 264 | self.participants.insert(name, info); 265 | } 266 | }, 267 | Err(_e) => { 268 | 269 | } 270 | } 271 | 272 | 273 | self.participant_names = self.participants.iter() 274 | .map(|(string, _)| string.clone()) 275 | .collect() 276 | ; 277 | 278 | self.participant_names.sort(); 279 | 280 | let participant_items: Vec<_> = self.participant_names.iter() 281 | .map(|string| ListItem::new(string.as_str()) 282 | .style(Style::default() 283 | .fg( 284 | self.participants.get_by_left(string).unwrap().status.to_color() 285 | ))) 286 | .collect(); 287 | 288 | let state = & mut self.participants_state; 289 | //let state = & mut self.participants_state; 290 | 291 | let text = match &self.selected_participant { 292 | Some(name) => { 293 | match self.participants.get_by_left(name) { 294 | Some(info) => { 295 | Text::from(vec![ 296 | Spans::from(format!("Name: {}", name)), 297 | Spans::from(format!("Endpoint: {}", info.endpoint)), 298 | Spans::from(vec![/*format!("Progress: {:?}", info.progress)*/ 299 | Span::raw("Status: "), 300 | Span::styled(format!("{:?}", info.status), Style::default().fg(info.status.to_color())) 301 | ]), 302 | Spans::from(format!("Progress: {}", 303 | match info.progress { 304 | Some(number) => format!("{}%", number as f32 / 100.0f32), 305 | None => format!("-") 306 | } 307 | )), 308 | ]) 309 | } 310 | None => Text::raw("") 311 | } 312 | } 313 | None => { 314 | Text::raw("") 315 | } 316 | }; 317 | 318 | 319 | let top_scroll = self.top_scroll_log_item; 320 | 321 | let messages_items: Vec<_> = self.logs.iter().map(|entry| { 322 | entry.to_listitem() 323 | }).collect(); 324 | 325 | 326 | //Generate the UI 327 | self.terminal.draw(|f| { 328 | let v_chunks = Layout::default() 329 | .direction(Direction::Vertical) 330 | .margin(1) 331 | .constraints( 332 | [ 333 | Constraint::Percentage(80), 334 | Constraint::Percentage(19), 335 | Constraint::Length(1), 336 | ].as_ref() 337 | ) 338 | .split(f.size()); 339 | 340 | let h_chunks = Layout::default() 341 | .direction(Direction::Horizontal) 342 | .constraints( 343 | [ 344 | Constraint::Percentage(20), 345 | Constraint::Percentage(80) 346 | ].as_ref() 347 | ) 348 | .split(v_chunks[0]); 349 | 350 | let participant_list = List::new(participant_items) 351 | .block(Block::default().title("Participants").borders(Borders::ALL)) 352 | .style(Style::default().fg(Color::White)) 353 | 354 | .highlight_style(Style::default() 355 | .bg(Color::Rgb(50, 50, 50))) 356 | .highlight_symbol(""); 357 | 358 | 359 | 360 | let messages_slice = &messages_items[top_scroll..]; 361 | 362 | 363 | 364 | f.render_stateful_widget(participant_list, h_chunks[0], state); 365 | 366 | let log_table = Table::new(messages_slice.to_vec()) 367 | .header( 368 | Row::new(vec!["Time", "Level", "Target", "Message"]) 369 | .style(Style::default().fg(Color::Rgb(229, 228, 226))) 370 | .bottom_margin(1) 371 | ) 372 | .widths(&[Constraint::Length(19), Constraint::Length(9), Constraint::Length(12), Constraint::Length(500)]) 373 | .column_spacing(1) 374 | .block(Block::default().title("Logs").borders(Borders::ALL)) 375 | .style(Style::default().fg(Color::White)); 376 | 377 | f.render_widget(log_table, h_chunks[1]); 378 | 379 | let info = Paragraph::new(text.clone()) 380 | .block(Block::default().title("Info").borders(Borders::ALL)) 381 | ; 382 | f.render_widget(info, v_chunks[1]); 383 | 384 | let shortcuts = Paragraph::new(Text::from(vec![Spans::from(vec![ 385 | Span::raw("q "), 386 | Span::styled("Exit ", Style::default().fg(Color::Rgb(58, 47, 77))), 387 | Span::raw("e "), 388 | Span::styled("Execute ", Style::default().fg(Color::Rgb(58, 47, 77))), 389 | Span::raw("p "), 390 | Span::styled("Pause ", Style::default().fg(Color::Rgb(58, 47, 77))), 391 | Span::raw("l "), 392 | Span::styled("Play ", Style::default().fg(Color::Rgb(58, 47, 77))), 393 | Span::raw("k "), 394 | Span::styled("Kill ", Style::default().fg(Color::Rgb(58, 47, 77))), 395 | Span::raw("c "), 396 | Span::styled("Clear Log ", Style::default().fg(Color::Rgb(58, 47, 77))), 397 | Span::raw("PgDn "), 398 | Span::styled("Scroll down log ", Style::default().fg(Color::Rgb(58, 47, 77))), 399 | Span::raw("PgUp "), 400 | Span::styled("Scroll up log ", Style::default().fg(Color::Rgb(58, 47, 77))), 401 | ])])).block(Block::default()); 402 | 403 | f.render_widget(shortcuts, v_chunks[2]); 404 | 405 | }).unwrap(); 406 | self.terminal.autoresize().unwrap(); 407 | 408 | 409 | Ok(()) 410 | 411 | } 412 | } 413 | 414 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). 5 | 6 | ## [Unreleased] 7 | ### To Do 8 | - When dealing with tables returned by Lua, make sure none of th key-data pairs are `LuaOther` as these will not be converted correctly. Warn user that tables within tables are not yet supported. 9 | - Upgrade to messages-io `0.10.0` 10 | 11 | ### Unfinished Ideas 12 | - Can we use `AnyLuaValue` type to store tables? 13 | - Experiment with the `LuaArray` option to see if we can use this as a table 14 | - See if rlua supports tables of tables, if it does migrate to rlua. 15 | - Use the Gague widget to show progress? 16 | - Can we selectively pick the parts of the dependencies we need instead of loading all of it? 17 | - Think of a good way to select scripts while running? 18 | - At the moment the user can just modify the script (thge one chosen when the application started) since executing will load the modified scriptc 19 | 20 | ### Changed 21 | - Replaced nightly code with crate `num_cpus` 22 | 23 | ## [0.2.18] - 2021-03-07 24 | 25 | ### Added 26 | - We now make sure the participants are idle before we start execution 27 | - Error handling for script loading error and bad script 28 | - Error handling for various types of script-based errors 29 | 30 | ### Fixed 31 | - Word 'client' changed to 'participant' for clarity and uniformity 32 | - Selecting up or down key with no participants no longer causes a panic 33 | - Now if Lua script functions fail, or throw an error we handle the error and add an entry to the log 34 | 35 | ### Changed 36 | - SendCode, Execute and SendData commands removed in favour of direct functions that return `Result` 37 | 38 | ## [0.2.17] - 2021-03-07 39 | 40 | ### Added 41 | - Correct image used in readme 42 | 43 | ## [0.2.16] - 2021-03-07 44 | 45 | ### Added 46 | - Scroll up/down added for log events 47 | - PgUp and PgDn shortcuts added to shortcut bar 48 | - Added binaries to SourceForge and added entry to readme 49 | 50 | ### Changed 51 | - Threads are numbered with leading zeros when named 52 | - Bounds checking on participant list removed to make use of TUI's built in scrolling for participant list 53 | 54 | ## [0.2.15] - 2021-03-07 55 | 56 | ### Added 57 | - Decent looking color palette 58 | - `Starting` Severity for when we start execution 59 | 60 | ### Fixed 61 | - Before we quit we remove all endpoints to avoid panic in thread 'message-io: tcp-adapter' 62 | - Cleared warnings 63 | 64 | ### Removed 65 | - We no longer print log message when a client connects (as a connecting client could be a test to determine whether to connect the real participants) 66 | - Removed old commented code 67 | 68 | ### Changed 69 | - Highlighting now changes background colour of highlighted text to dark grey 70 | - We now only send the endpoint AND name in messages that need it 71 | 72 | 73 | 74 | ## [0.2.14] - 2021-03-04 75 | ### Added 76 | - First participant is automatically selected when first available 77 | - Renamed stop to kill 78 | - Kill command now adds entry to log 79 | - Kill works by calling `std::process::exit` instead of panicking 80 | - Before `Participant` struct connects, we check to make sure a host is available. we do this by creating a temporary client to connect to host. 81 | - When a device disconnects, we make sure it has registered before we try and remove it. 82 | - Participant will now sit and wait until the host is available 83 | 84 | ### Fixed 85 | - When the network monitor thread call to recv fails, the thread exits 86 | 87 | ## [0.2.13] - 2021-03-03 88 | ### Added 89 | - `_print` to allow user to display messages in the Host ui from participants. Can only be called in `execute_code` function 90 | 91 | ### Changed 92 | - `_progress` now ensures that the progress update cannot be sent too frequently by choosing a duration in milliseconds between progress updates (users should still minimise how often this function is called though) 93 | - Since progress must be stored as an integer (to derive Hash for ParticipantInfo) we multiply the percentage by 100 when we store it, to give us 2 decimal places 94 | - Selecting from a list now shows info in TUI (no need to press enter each time) 95 | - Updated readme with extra, more helpful information 96 | - Starting the server now adds an entry to the log 97 | - All println! calls in host changed to eprintln! 98 | - We now quit the host properly, without panicking 99 | - Shortcut added to clear log 100 | 101 | ## [0.2.12] - 2021-03-03 102 | ### Added 103 | - List state for selecting participants and showing information 104 | - `Result` severity for highlighting when a result is returned from execution 105 | 106 | ### Fixed 107 | - Pausing and playing while idle will no longer change the status, since participants must inform the host when they pause/unpause 108 | 109 | ### Removed 110 | - Actions panel in favour of keyboard shortcuts 111 | 112 | ## [0.2.11] - 2021-03-02 113 | ### Added 114 | - Added MIT license 115 | - Tui has been added showing participants and events 116 | - All println! commands removed from `Host` and converted to `Panel` events 117 | - Status of participants now added, colours indicate status 118 | - Errors, warnings and info sent to or created by `Host` are also showed in the Tui 119 | - Structs and enums for storing information on participants in `Panel` and functions to convert to TUI objects 120 | - Shortcut bar at bottom of ui showing all the available shortcuts 121 | 122 | ### Changed 123 | - Updated readme to reflect changes to command line options 124 | - Using stop when participants are idling now stops them 125 | 126 | ## [0.2.10] - 2021-02-28 127 | ### Added 128 | - `Participant::recv_message` function simplifies the processing of incoming messages and the possible errors that can occur 129 | 130 | ### Removed 131 | - `capture` dependency removed as it wasn't used enough to warrant it 132 | 133 | ### Changed 134 | - We use crossbeam's `scope` to allow us to use references in spawned threads, meaning we don't have to clone the `participant_name` and `ip_address` strings for each thread 135 | - Modified the `thread count` option such that when it is used the user specifies the number of threads, and when it is omitted we use all available concurrency 136 | 137 | ## [0.2.9] - 2021-02-26 138 | 139 | ### Changed 140 | - Stop message has been changed to kill, which forcibly removes the participant during execution 141 | - Updated readme.md 142 | 143 | ### Added 144 | - Code to handle pause/play and 145 | 146 | ## [0.2.8] - 2021-02-25 147 | ### Added 148 | - Proof of concept for play/pause/stop commands and progress 149 | - Temporary code to `Host` to process a `Message::Progress` message 150 | - _progress and _check functions to Lua test script 151 | 152 | ### Changed 153 | - Adding a monitoring thread to `Participant` means that the network is effectively multiple sender single consumer 154 | 155 | ### Removed 156 | - `Event` enum in participant.rs since it is only relevant for GUI code 157 | 158 | ## [0.2.7] - 2021-02-24 159 | ### Changed 160 | - Migrated from std `mpsc` to crossbeam-channel message passing which allow us to move a cloned receiver to the `check` closure 161 | - Renamed `Participant::check_events` to `Participant::tick` 162 | 163 | ### Added 164 | - Extra keystrokes to temporary ui code, that allows us to send pause, start and stop signals 165 | - `Message::PlayAll`, `Message::StopAll` and `Message::PauseAll` messages 166 | 167 | ## [0.2.6] - 2021-02-23 168 | ### Changed 169 | - Migrated from message-io's `EventQueue` to `mpsc` message passing 170 | 171 | ### Added 172 | - `Host::check_events` method now executes in saparate thread alongside the main thread which takes care of the ui. 173 | 174 | ## [0.2.5] - 2021-02-21 175 | 176 | ### Added 177 | - `Host::new` returns a `Result` that indicates if the starting of the server failed or succeeded 178 | 179 | ### Changed 180 | - `Host::participants_finished` field is reset when the `Host::start_participants` is called, not when the data is received 181 | - All participants are executed on spawned threads, the main thread now only waits for spawned threads 182 | - Certain println! messages migrated from `Host` methods to `Panel` events 183 | - Host events moved into messages.rs 184 | - `ParticipantStatus` enum added and implemented for `Idle` and `Calculating` 185 | - `Participant::check_events` now returns `Result` so if the function fails, the thread can terminate. If all threads terminate, the application ends. 186 | 187 | ## [0.2.4] - 2021-02-21 188 | 189 | ### Added 190 | - `Host::participants_startedwith` field added to keep track of the number of participants registered when the computation began. 191 | - We now use `thread::available_concurrency` to automatically select the number of threads to use (use -t or --threads without specifying a number) 192 | - Temporary key-press commands added (`e` for execution, `d` for displaying participants and `c` for showing how many participants are connected.) 193 | 194 | ### Changed 195 | - Less debuggy, less verbose printed messages from `Host` 196 | - All spawned threads in participant nodes are named based on the supplied participant name 197 | 198 | ### Fixed 199 | - Trying to add a participant with a name that already exists will cause the offending participant to be disconnected 200 | - We verify that the host acts correctly when a `ParticipantError` is received, namely 201 | - The offending participant is removed from the participant list, so no more calculations can take place 202 | - Host now stops computation if the participants have changed since the start of the computation (i.e. any participants have connected/disconnected) 203 | 204 | ## [0.2.3] - 2021-02-20 205 | ### Added 206 | - Multithreading added, so multiple participants can be executed from a single process 207 | - Command line arguments for the number of threads to spawn and the name of participant(s) 208 | - `Host::participants` has been changed to a bidirectional map for easy insertion and deletion from either string name or the endpoint 209 | - If multiple threads are used, then the number of the thread (arbitrary) is appended to the --name to ensure each participant has a unique name 210 | - The participant name is now sent in the `Messages::Register` message 211 | - Host `Message` matching now includes arms for `ParticipantError` and `ParticipantWarning` 212 | 213 | ## [0.2.2] - 2021-02-20 214 | ### Added 215 | - Host and participant selection via clap now uses subcommands, and --script option is only required for host subcommand 216 | - --script validator added 217 | - Error handling implemented for participant 218 | - Extra messages added for sending strings from participant to host 219 | 220 | ### Fixed 221 | - Various warnings cleared (Pause, play and stop warnings still exist as these are not yet implemented) 222 | 223 | ## [0.2.1] - 2021-02-20 224 | ### Added 225 | - We now work with `SerdeLuaTable`, `Vec<(AnyLuaValue, AnyLuaValue)>`, to allow user to pass tables between host and participant 226 | - Modified host code so that the results global variable (used by `interpret_results`) is now an array of tables, each table coming from a participant 227 | - Added lua.rs to handle common hlua (or rlua) code 228 | 229 | ### Changed 230 | - Modified `AnyLuaValue` to be serializable 231 | 232 | ### Removed 233 | - We no longer store the incoming data in Host as it is sent directly to Lua context 234 | 235 | ## [0.2.0] - 2021-02-19 236 | ### Removed 237 | - `stack_vm` has been removed in favor of a Lua script 238 | 239 | ### Added 240 | - Clap now accepts a --script command line argument 241 | - hlua which handles the Lua compiling 242 | - Host and Participants now call their function from the Lua script 243 | - `interpret_results` now returns a string 244 | - Simple prime divisibility algorithm implemented as test Lua script 245 | 246 | ### Changed 247 | - Certain Send events (within the host) now send data/code to all endpoints. 248 | NOTE: This may change in the future. 249 | - Code loading and execution now two separate events 250 | - Host contains data field containing all data sent from all participants (created by each participants execution of `execute_code`) 251 | 252 | ## [0.1.8] - 2021-02-18 253 | ### Fixed 254 | - Timeout on event_queue receive_timeout is reduced to 1micro second to speed up vm. This event is executed very quickly, and really only servers to check if an event has been sent 255 | - Cleared various 'unused import' warnings 256 | 257 | ### Added 258 | - Host::test_participant_event now works for an arbitrary number of participants 259 | - Added readme 260 | - Added print_v for printing contents of variables 261 | - Added compile_file function 262 | 263 | ### Changed 264 | - compile_file and compile_source added to Compiler impl 265 | 266 | ### Removed 267 | - Removed test() function in compiler.rs 268 | 269 | ## [0.1.7] - 2021-02-17 270 | ### Added 271 | - We now declare lazy statics in a more global sense to avoid having to pass them around 272 | - mod instruction added, and is used in the sample_code 273 | - Sample code now uses a jz instruction in loop 274 | - Compiler.rs now contains an iterator that walks over each line in source and outputs a Statement enum 275 | - The statement enum is then interpreted and added to the builder 276 | - print_s instruction now prints the entire stack instead of just the top 277 | 278 | ## [0.1.6] - 2021-02-15 279 | ### Added 280 | - Added functionality to enable play, pause and stopping of the VM 281 | - Better changelog created 282 | - Bulk of the work for compiler is complete 283 | - Enums for statements, tokens, etc. 284 | - Conversion from string into enums 285 | - Thinking behind strategy documented in compiler.rs 286 | - Regexes added for matching 287 | - Extensive use of regex and slice pattern matching 288 | 289 | ### Fixed 290 | - \_d functions now use Machine::heap field instead of local variables 291 | 292 | ## [0.1.5] - 2021-02-15 293 | ### Added 294 | - We now automatically pull the version number from Cargo.toml for use in clap 295 | - stack_vm now modified into iterator that allows us to step instructions 296 | - stack_vm modified to allow a heap 297 | - New stack_vm added to github, can be found [here](https://github.com/ray33ee/stack-vm) 298 | - Participant now contains the machine as a field 299 | - Each call of check_events will call Machine::next() if the machine is loaded 300 | - Stepping over machine prevents the blocking affect of Machine::run 301 | - Participant::check_events now uses EventQueue::receive_timeout so we can execute in within main loop 302 | 303 | ## [0.1.4] - 2021-02-14 304 | ### Added 305 | - Logical numbering of opcodes grouping similar instructions together and leaving gaps for future codes 306 | - Added hash map for constants (to be used by compiler) 307 | - Added conversion from Operand to f64 (for use in f64 functions) 308 | - Sample source code for MidasVM has now been added to outline what source code will look like 309 | 310 | ### Fixed 311 | - Now we use SocketAddrV4 to validate IP addresses (with port number) 312 | 313 | ## [0.1.3] - 2021-02-13 314 | ### Added 315 | - Simple address command line option for both host and participant 316 | - We now work with Vec instead of two different vectors 317 | - When the MidasVM finishes, it sends what's left of its stack to the host 318 | - Added a bunch of useful constants from std::f64::consts 319 | - We now have a way to transfer data to participant independent of the Code. We do this by moving the data on to a list of local variables addressed from 'd_0' 320 | - Added more mov functions (movdl, movds, movdv and movc) 321 | - Constants declared in instructions.rs 322 | 323 | ### Fixed 324 | - Copy trait added to Operand, cleared various borrow-check warnings 325 | 326 | ## [0.1.2] - 2021-02-12 327 | ### Added 328 | - Can now send code from host to participant 329 | - Modified code in Host and Participant structs to setup in new() and check in check_events 330 | - Participant and Host now check network for the various types of messages outlined in Message enum 331 | - Cleared various warnings 332 | - -mode argument now validated with possible_values function instead of custom validator 333 | - Participant to Host Vectors now include an i64, used to identify if needed 334 | 335 | ## [0.1.1] - 2021-02-11 336 | ### Added 337 | - Added very simple client-server network connection 338 | - Added various modules for network host, participant, common code(messages.rs) and compiler option 339 | - StackVM code is now serializable 340 | - Host or participant mode now selected via command line arg 341 | 342 | ## [0.1.0] - 2021-02-10 343 | ### Added 344 | - Initial commit 345 | -------------------------------------------------------------------------------- /src/host.rs: -------------------------------------------------------------------------------- 1 | use crate::messages::{Message, UiEvents, ParticipantStatus, Severity, NodeType}; 2 | 3 | use message_io::network::Endpoint; 4 | 5 | use bimap::BiMap; 6 | 7 | use message_io::network::{Network, NetEvent, Transport}; 8 | 9 | use hlua::{Lua, AnyLuaValue, LuaTable, LuaFunctionCallError}; 10 | use std::io::Read; 11 | 12 | use crate::lua::SerdeLuaTable; 13 | 14 | use crate::messages::HostEvent; 15 | use crossbeam_channel::{Receiver, Sender}; 16 | 17 | 18 | pub struct Host<'a> { 19 | participants: BiMap, 20 | //event_queue: EventQueue, 21 | network: Network, 22 | 23 | //ui_sender: Option>, 24 | 25 | command_receiver: Receiver, 26 | message_sender: Sender, 27 | 28 | participants_finished: usize, 29 | participants_startedwith: BiMap, 30 | 31 | 32 | lua: Lua<'a> 33 | } 34 | 35 | impl<'a> Host<'a> { 36 | 37 | pub fn new(command_receiver: Receiver, 38 | command_sender: Sender, 39 | message_sender: Sender, 40 | server_address: &str) -> Result { 41 | 42 | let network_sender = command_sender.clone(); 43 | 44 | let mut network = Network::new(move |net_event| network_sender.send(HostEvent::Network(net_event)).unwrap()); 45 | 46 | let mut lua = Lua::new(); 47 | 48 | lua.openlibs(); 49 | 50 | match network.listen(Transport::Tcp, server_address) { 51 | Ok(_) => message_sender.send(UiEvents::Log(NodeType::Host, format!("Host running at {}", server_address), Severity::Info)).unwrap(), 52 | Err(e) => return Err(format!("Can not listen at {} - {}", server_address, e)) 53 | }; 54 | 55 | Ok(Host { 56 | participants: BiMap::new(), 57 | command_receiver, 58 | network, 59 | participants_finished: 0, 60 | participants_startedwith: BiMap::new(), 61 | message_sender, 62 | lua 63 | }) 64 | } 65 | 66 | fn send_data(& mut self) -> Result<(), String> { 67 | //Extract the 'generate data' function from the Lua script. 68 | let generate_data_option: Option> = self.lua.get("generate_data"); 69 | 70 | match generate_data_option { 71 | Some(mut generate_data) => { 72 | let endpoint_count = self.participants.len(); 73 | 74 | 75 | //Call generate_data function for each endpoint, and send the resultant data 76 | for (i, (_name, endpoint)) in self.participants.iter().enumerate() { 77 | let result_option: Result, _> = generate_data.call_with_args((i as i32, endpoint_count as i32)); 78 | 79 | match result_option { 80 | Ok(mut result) => { 81 | let list: SerdeLuaTable = result.iter::().map(|pair| pair.unwrap()).collect(); 82 | 83 | 84 | self.network.send(*endpoint, Message::VectorHTP(list)); 85 | } 86 | Err(e) => { 87 | 88 | 89 | return match e { 90 | LuaFunctionCallError::LuaError(e) => { 91 | Err(format!("Error in `generate_data` function - {}", e)) 92 | } 93 | LuaFunctionCallError::PushError(e) => { 94 | Err(format!("Error in `generate_data` function - PushError: {:?}", e)) 95 | } 96 | } 97 | 98 | } 99 | } 100 | 101 | 102 | } 103 | 104 | Ok(()) 105 | } 106 | None => { 107 | Err(format!("`generate_data` function does not exist in script.")) 108 | } 109 | } 110 | 111 | 112 | } 113 | 114 | fn send_code(& mut self, code: String) { 115 | for (_name, endpoint) in self.participants.iter() { 116 | self.network.send(*endpoint, Message::Code(code.clone())); 117 | } 118 | } 119 | 120 | fn execute(& mut self) { 121 | for (_, endpoint) in self.participants.iter() { 122 | self.network.send(*endpoint, Message::Execute); 123 | } 124 | } 125 | 126 | pub fn start_participants(& mut self, path: &str) { 127 | 128 | 129 | use std::fs::File; 130 | 131 | match File::open(path) { 132 | Ok(mut fh) => { 133 | 134 | 135 | let mut source_code = String::new(); 136 | 137 | match fh.read_to_string(&mut source_code) { 138 | Ok(_) => { 139 | let message_sender = self.message_sender.clone(); 140 | 141 | self.lua.set("_print", hlua::function1(move |message: String| { 142 | message_sender.send(UiEvents::Log(NodeType::Host, message, Severity::Stdout)).unwrap(); 143 | })); 144 | 145 | match self.lua.execute::<()>(source_code.as_str()) { 146 | Ok(_) => { 147 | 148 | self.message_sender.send(UiEvents::Log(NodeType::Host, format!("Starting calculations on {} participants.", self.participants.len()), Severity::Starting)).unwrap(); 149 | 150 | self.participants_finished = 0; 151 | 152 | self.participants_startedwith = self.participants.clone(); 153 | 154 | match self.send_data() { 155 | Ok(_) => { 156 | 157 | 158 | self.send_code(source_code); 159 | 160 | self.execute(); 161 | } 162 | Err(e) => { 163 | self.message_sender.send(UiEvents::Log(NodeType::Host, e, Severity::Error)).unwrap(); 164 | 165 | } 166 | } 167 | } 168 | Err(e) => { 169 | self.message_sender.send(UiEvents::Log(NodeType::Host, format!("Bad Lua script - {}", e), Severity::Error)).unwrap(); 170 | 171 | } 172 | } 173 | } 174 | Err(e) => { 175 | self.message_sender.send(UiEvents::Log(NodeType::Host, format!("Error parsing script - {}", e), Severity::Error)).unwrap(); 176 | 177 | } 178 | } 179 | 180 | 181 | 182 | 183 | } 184 | Err(e) => { 185 | self.message_sender.send(UiEvents::Log(NodeType::Host, format!("Error opening script - {}", e), Severity::Error)).unwrap(); 186 | 187 | } 188 | } 189 | 190 | 191 | 192 | 193 | } 194 | 195 | pub fn check_events(& mut self) { 196 | 197 | match self.command_receiver.recv(/*Duration::from_micros(0)*/) { 198 | Ok(event) => match event { 199 | HostEvent::Network(net_event) => match net_event { 200 | NetEvent::Message(endpoint, message) => { 201 | 202 | 203 | match message { 204 | Message::Register(name) => { 205 | if self.participants.contains_left(&name) { 206 | self.message_sender.send(UiEvents::Log(NodeType::Participant(name.clone()), format!("Could not register participant due to name conflict"), Severity::Warning)).unwrap(); 207 | self.network.remove_resource(endpoint.resource_id()); 208 | } 209 | else { 210 | self.participants.insert(name.clone(), endpoint); 211 | self.message_sender.send(UiEvents::ParticipantRegistered(endpoint, name.clone())).unwrap(); 212 | //self.message_sender.send(UiEvents::ChangeStatusTo(ParticipantStatus::Idle, endpoint, name)).unwrap(); 213 | 214 | 215 | } 216 | }, 217 | Message::Unregister => { 218 | { 219 | let endpoint_name = self.participants.get_by_right(&endpoint).unwrap(); 220 | self.message_sender.send(UiEvents::ParticipantUnregistered(endpoint_name.clone())).unwrap(); 221 | } 222 | self.participants.remove_by_right(&endpoint); 223 | }, 224 | Message::VectorPTH(data) => { 225 | 226 | let endpoint_name = self.participants.get_by_right(&endpoint).unwrap(); 227 | 228 | self.message_sender.send(UiEvents::ChangeStatusTo(ParticipantStatus::Idle, endpoint, endpoint_name.clone())).unwrap(); 229 | 230 | if self.participants_startedwith != self.participants { 231 | self.message_sender.send(UiEvents::Log(NodeType::Participant(endpoint_name.clone()), format!("Some participants have disconnected/connected before execution could complete."), Severity::Error)).unwrap(); 232 | 233 | } 234 | else { 235 | //A participant has finished, so increment the count 236 | self.participants_finished += 1; 237 | 238 | //If this is the first participant, initialise the results variable 239 | if self.participants_finished == 1 { 240 | self.lua.empty_array("results"); 241 | } 242 | 243 | { 244 | //Create temporary global array called 'tmp_table' 245 | let mut arr = self.lua.empty_array("tmp_table"); 246 | 247 | // Copy data to temporary array 248 | for (_, value) in data.iter().enumerate() { 249 | arr.set(value.0.clone(), value.1.clone()); 250 | } 251 | } 252 | 253 | //Move the temporary table to to the global results 254 | self.lua.execute::<()>(format!("results[{}] = tmp_table", self.participants_finished).as_str()).unwrap(); 255 | 256 | // Test to see if all participants have finished 257 | if self.participants.len() == self.participants_finished { 258 | 259 | let interpret_results_option: Option> = self.lua.get("interpret_results"); 260 | 261 | match interpret_results_option { 262 | Some(mut interpret_results) => { 263 | // Get return value 264 | match interpret_results.call::() { 265 | Ok(return_code) => { 266 | 267 | self.message_sender.send(UiEvents::InterpretResultsReturn(return_code)).unwrap(); 268 | } 269 | Err(e) => { 270 | self.message_sender.send(UiEvents::Log(NodeType::Host, format!("Error in `interpret_results` function - {}", e), Severity::Error)).unwrap(); 271 | 272 | } 273 | } 274 | 275 | } 276 | None => { 277 | 278 | self.message_sender.send(UiEvents::Log(NodeType::Host, format!("`interpret_results` function does not exist in script."), Severity::Error)).unwrap(); 279 | } 280 | } 281 | 282 | 283 | 284 | 285 | } 286 | } 287 | }, 288 | Message::ParticipantError(err) => { 289 | let endpoint_name = self.participants.get_by_right(&endpoint).unwrap(); 290 | self.message_sender.send(UiEvents::Log(NodeType::Participant(endpoint_name.clone()), err, Severity::Error)).unwrap(); 291 | }, 292 | Message::ParticipantWarning(err) => { 293 | let endpoint_name = self.participants.get_by_right(&endpoint).unwrap(); 294 | self.message_sender.send(UiEvents::Log(NodeType::Participant(endpoint_name.clone()), err, Severity::Warning)).unwrap(); 295 | }, 296 | Message::Whisper(err) => { 297 | let endpoint_name = self.participants.get_by_right(&endpoint).unwrap(); 298 | self.message_sender.send(UiEvents::Log(NodeType::Participant(endpoint_name.clone()), err, Severity::Info)).unwrap(); 299 | }, 300 | Message::Progress(progress) => { 301 | let endpoint_name = self.participants.get_by_right(&endpoint).unwrap(); 302 | self.message_sender.send(UiEvents::ParticipantProgress(endpoint_name.clone(),progress)).unwrap(); 303 | 304 | }, 305 | Message::Paused => { 306 | let endpoint_name = self.participants.get_by_right(&endpoint).unwrap(); 307 | self.message_sender.send(UiEvents::ChangeStatusTo(ParticipantStatus::Paused, endpoint, endpoint_name.clone())).unwrap(); 308 | 309 | }, 310 | Message::Executing => { 311 | let endpoint_name = self.participants.get_by_right(&endpoint).unwrap(); 312 | self.message_sender.send(UiEvents::ChangeStatusTo(ParticipantStatus::Calculating, endpoint, endpoint_name.clone())).unwrap(); 313 | 314 | }, 315 | Message::Stdout(output) => { 316 | let endpoint_name = self.participants.get_by_right(&endpoint).unwrap(); 317 | self.message_sender.send(UiEvents::Log(NodeType::Participant(endpoint_name.clone()), output, Severity::Stdout)).unwrap(); 318 | 319 | } 320 | _ => { 321 | 322 | panic!("Invalid message received by host ({:?})", message); 323 | } 324 | } 325 | } 326 | NetEvent::AddedEndpoint(_endpoint) => { 327 | //Participant has connected to the host, but at this stage has not yet registered 328 | //self.message_sender.send(UiEvents::Log(NodeType::Host, format!("Participant added: {}", endpoint), Severity::Info)).unwrap(); 329 | 330 | }, 331 | NetEvent::RemovedEndpoint(endpoint) => { 332 | //Participant disconnected without unregistering 333 | match self.participants.get_by_right(&endpoint) 334 | { 335 | Some(endpoint_name) => { 336 | 337 | self.message_sender.send(UiEvents::ParticipantUnregistered(endpoint_name.clone())).unwrap(); 338 | 339 | self.participants.remove_by_right(&endpoint); 340 | } 341 | None => { 342 | 343 | } 344 | } 345 | 346 | 347 | } 348 | NetEvent::DeserializationError(_) => (), 349 | }, 350 | HostEvent::Pause(endpoint) => { 351 | self.network.send(endpoint, Message::Pause); 352 | 353 | }, 354 | HostEvent::Play(endpoint) => { 355 | self.network.send(endpoint, Message::Play); 356 | 357 | }, 358 | HostEvent::Kill(endpoint) => { 359 | self.network.send(endpoint, Message::Kill); 360 | }, 361 | HostEvent::Begin(path) => { 362 | self.start_participants(path.as_str()); 363 | }, 364 | 365 | HostEvent::PlayAll => { 366 | for (_, endpoint) in self.participants.iter() { 367 | self.network.send(*endpoint, Message::Play); 368 | 369 | } 370 | }, 371 | 372 | HostEvent::PauseAll => { 373 | for (_, endpoint) in self.participants.iter() { 374 | self.network.send(*endpoint, Message::Pause); 375 | 376 | } 377 | }, 378 | 379 | HostEvent::KillAll => { 380 | for (_, endpoint) in self.participants.iter() { 381 | self.network.send(*endpoint, Message::Kill); 382 | } 383 | }, 384 | 385 | HostEvent::RemoveAll => { 386 | for (_, endpoint) in self.participants.iter() { 387 | self.network.remove_resource(endpoint.resource_id()); 388 | } 389 | }, 390 | }, 391 | Err(e) => { 392 | eprintln!("Receive error in Host::check_events() - {}", e); 393 | } 394 | } 395 | 396 | 397 | } 398 | 399 | } 400 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "0.6.10" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "81ce3d38065e618af2d7b77e10c5ad9a069859b4be3c2250f674af3840d9c8a5" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "ansi_term" 16 | version = "0.11.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" 19 | dependencies = [ 20 | "winapi", 21 | ] 22 | 23 | [[package]] 24 | name = "atty" 25 | version = "0.2.14" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 28 | dependencies = [ 29 | "hermit-abi", 30 | "libc", 31 | "winapi", 32 | ] 33 | 34 | [[package]] 35 | name = "autocfg" 36 | version = "1.0.1" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 39 | 40 | [[package]] 41 | name = "bimap" 42 | version = "0.6.0" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | checksum = "f92b72b8f03128773278bf74418b9205f3d2a12c39a61f92395f47af390c32bf" 45 | 46 | [[package]] 47 | name = "bincode" 48 | version = "1.3.1" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "f30d3a39baa26f9651f17b375061f3233dde33424a8b72b0dbe93a68a0bc896d" 51 | dependencies = [ 52 | "byteorder", 53 | "serde", 54 | ] 55 | 56 | [[package]] 57 | name = "bitflags" 58 | version = "1.2.1" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 61 | 62 | [[package]] 63 | name = "byteorder" 64 | version = "1.4.2" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b" 67 | 68 | [[package]] 69 | name = "cassowary" 70 | version = "0.3.0" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" 73 | 74 | [[package]] 75 | name = "cc" 76 | version = "1.0.66" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48" 79 | 80 | [[package]] 81 | name = "cfg-if" 82 | version = "0.1.10" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 85 | 86 | [[package]] 87 | name = "cfg-if" 88 | version = "1.0.0" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 91 | 92 | [[package]] 93 | name = "chrono" 94 | version = "0.4.19" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" 97 | dependencies = [ 98 | "libc", 99 | "num-integer", 100 | "num-traits 0.2.14", 101 | "time", 102 | "winapi", 103 | ] 104 | 105 | [[package]] 106 | name = "clap" 107 | version = "2.33.3" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" 110 | dependencies = [ 111 | "ansi_term", 112 | "atty", 113 | "bitflags", 114 | "strsim", 115 | "textwrap", 116 | "unicode-width", 117 | "vec_map", 118 | ] 119 | 120 | [[package]] 121 | name = "const_fn" 122 | version = "0.4.5" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | checksum = "28b9d6de7f49e22cf97ad17fc4036ece69300032f45f78f30b4a4482cdc3f4a6" 125 | 126 | [[package]] 127 | name = "crossbeam" 128 | version = "0.8.0" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "fd01a6eb3daaafa260f6fc94c3a6c36390abc2080e38e3e34ced87393fb77d80" 131 | dependencies = [ 132 | "cfg-if 1.0.0", 133 | "crossbeam-channel", 134 | "crossbeam-deque", 135 | "crossbeam-epoch", 136 | "crossbeam-queue", 137 | "crossbeam-utils", 138 | ] 139 | 140 | [[package]] 141 | name = "crossbeam-channel" 142 | version = "0.5.0" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775" 145 | dependencies = [ 146 | "cfg-if 1.0.0", 147 | "crossbeam-utils", 148 | ] 149 | 150 | [[package]] 151 | name = "crossbeam-deque" 152 | version = "0.8.0" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9" 155 | dependencies = [ 156 | "cfg-if 1.0.0", 157 | "crossbeam-epoch", 158 | "crossbeam-utils", 159 | ] 160 | 161 | [[package]] 162 | name = "crossbeam-epoch" 163 | version = "0.9.1" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | checksum = "a1aaa739f95311c2c7887a76863f500026092fb1dce0161dab577e559ef3569d" 166 | dependencies = [ 167 | "cfg-if 1.0.0", 168 | "const_fn", 169 | "crossbeam-utils", 170 | "lazy_static", 171 | "memoffset", 172 | "scopeguard", 173 | ] 174 | 175 | [[package]] 176 | name = "crossbeam-queue" 177 | version = "0.3.1" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "0f6cb3c7f5b8e51bc3ebb73a2327ad4abdbd119dc13223f14f961d2f38486756" 180 | dependencies = [ 181 | "cfg-if 1.0.0", 182 | "crossbeam-utils", 183 | ] 184 | 185 | [[package]] 186 | name = "crossbeam-utils" 187 | version = "0.8.1" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d" 190 | dependencies = [ 191 | "autocfg", 192 | "cfg-if 1.0.0", 193 | "lazy_static", 194 | ] 195 | 196 | [[package]] 197 | name = "crossterm" 198 | version = "0.18.2" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | checksum = "4e86d73f2a0b407b5768d10a8c720cf5d2df49a9efc10ca09176d201ead4b7fb" 201 | dependencies = [ 202 | "bitflags", 203 | "crossterm_winapi 0.6.2", 204 | "lazy_static", 205 | "libc", 206 | "mio", 207 | "parking_lot", 208 | "signal-hook", 209 | "winapi", 210 | ] 211 | 212 | [[package]] 213 | name = "crossterm" 214 | version = "0.19.0" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "7c36c10130df424b2f3552fcc2ddcd9b28a27b1e54b358b45874f88d1ca6888c" 217 | dependencies = [ 218 | "bitflags", 219 | "crossterm_winapi 0.7.0", 220 | "lazy_static", 221 | "libc", 222 | "mio", 223 | "parking_lot", 224 | "signal-hook", 225 | "winapi", 226 | ] 227 | 228 | [[package]] 229 | name = "crossterm_winapi" 230 | version = "0.6.2" 231 | source = "registry+https://github.com/rust-lang/crates.io-index" 232 | checksum = "c2265c3f8e080075d9b6417aa72293fc71662f34b4af2612d8d1b074d29510db" 233 | dependencies = [ 234 | "winapi", 235 | ] 236 | 237 | [[package]] 238 | name = "crossterm_winapi" 239 | version = "0.7.0" 240 | source = "registry+https://github.com/rust-lang/crates.io-index" 241 | checksum = "0da8964ace4d3e4a044fd027919b2237000b24315a37c916f61809f1ff2140b9" 242 | dependencies = [ 243 | "winapi", 244 | ] 245 | 246 | [[package]] 247 | name = "derivative" 248 | version = "2.2.0" 249 | source = "registry+https://github.com/rust-lang/crates.io-index" 250 | checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" 251 | dependencies = [ 252 | "proc-macro2", 253 | "quote", 254 | "syn", 255 | ] 256 | 257 | [[package]] 258 | name = "fuchsia-cprng" 259 | version = "0.1.1" 260 | source = "registry+https://github.com/rust-lang/crates.io-index" 261 | checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" 262 | 263 | [[package]] 264 | name = "hermit-abi" 265 | version = "0.1.18" 266 | source = "registry+https://github.com/rust-lang/crates.io-index" 267 | checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" 268 | dependencies = [ 269 | "libc", 270 | ] 271 | 272 | [[package]] 273 | name = "hlua" 274 | version = "0.4.1" 275 | source = "git+https://github.com/ray33ee/hlua#13989ad42decfab172599803d327419e3d725653" 276 | dependencies = [ 277 | "libc", 278 | "lua52-sys", 279 | "serde", 280 | ] 281 | 282 | [[package]] 283 | name = "instant" 284 | version = "0.1.9" 285 | source = "registry+https://github.com/rust-lang/crates.io-index" 286 | checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" 287 | dependencies = [ 288 | "cfg-if 1.0.0", 289 | ] 290 | 291 | [[package]] 292 | name = "ipaddress" 293 | version = "0.1.2" 294 | source = "registry+https://github.com/rust-lang/crates.io-index" 295 | checksum = "c9e999e655a4d0ab9b8d21026760e5154d8adee48c9149c67f918f540f6a05f5" 296 | dependencies = [ 297 | "lazy_static", 298 | "libc", 299 | "num", 300 | "num-integer", 301 | "num-traits 0.1.43", 302 | "regex", 303 | ] 304 | 305 | [[package]] 306 | name = "lazy_static" 307 | version = "1.4.0" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 310 | 311 | [[package]] 312 | name = "libc" 313 | version = "0.2.86" 314 | source = "registry+https://github.com/rust-lang/crates.io-index" 315 | checksum = "b7282d924be3275cec7f6756ff4121987bc6481325397dde6ba3e7802b1a8b1c" 316 | 317 | [[package]] 318 | name = "lock_api" 319 | version = "0.4.2" 320 | source = "registry+https://github.com/rust-lang/crates.io-index" 321 | checksum = "dd96ffd135b2fd7b973ac026d28085defbe8983df057ced3eb4f2130b0831312" 322 | dependencies = [ 323 | "scopeguard", 324 | ] 325 | 326 | [[package]] 327 | name = "log" 328 | version = "0.4.14" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 331 | dependencies = [ 332 | "cfg-if 1.0.0", 333 | ] 334 | 335 | [[package]] 336 | name = "lua52-sys" 337 | version = "0.1.2" 338 | source = "git+https://github.com/ray33ee/hlua#13989ad42decfab172599803d327419e3d725653" 339 | dependencies = [ 340 | "cc", 341 | "libc", 342 | "pkg-config", 343 | ] 344 | 345 | [[package]] 346 | name = "memchr" 347 | version = "2.3.4" 348 | source = "registry+https://github.com/rust-lang/crates.io-index" 349 | checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" 350 | 351 | [[package]] 352 | name = "memoffset" 353 | version = "0.6.1" 354 | source = "registry+https://github.com/rust-lang/crates.io-index" 355 | checksum = "157b4208e3059a8f9e78d559edc658e13df41410cb3ae03979c83130067fdd87" 356 | dependencies = [ 357 | "autocfg", 358 | ] 359 | 360 | [[package]] 361 | name = "message-io" 362 | version = "0.6.0" 363 | source = "registry+https://github.com/rust-lang/crates.io-index" 364 | checksum = "783d34ca73b20da30e8eaa5cc3339d1df825e69671251a79510506bdb8ac87e4" 365 | dependencies = [ 366 | "bincode", 367 | "crossbeam", 368 | "lazy_static", 369 | "log", 370 | "mio", 371 | "net2", 372 | "num_enum", 373 | "serde", 374 | ] 375 | 376 | [[package]] 377 | name = "midas" 378 | version = "0.2.18" 379 | dependencies = [ 380 | "bimap", 381 | "chrono", 382 | "clap", 383 | "crossbeam", 384 | "crossbeam-channel", 385 | "crossterm 0.19.0", 386 | "hlua", 387 | "ipaddress", 388 | "message-io", 389 | "num_cpus", 390 | "serde", 391 | "tui", 392 | ] 393 | 394 | [[package]] 395 | name = "mio" 396 | version = "0.7.7" 397 | source = "registry+https://github.com/rust-lang/crates.io-index" 398 | checksum = "e50ae3f04d169fcc9bde0b547d1c205219b7157e07ded9c5aff03e0637cb3ed7" 399 | dependencies = [ 400 | "libc", 401 | "log", 402 | "miow", 403 | "ntapi", 404 | "winapi", 405 | ] 406 | 407 | [[package]] 408 | name = "miow" 409 | version = "0.3.6" 410 | source = "registry+https://github.com/rust-lang/crates.io-index" 411 | checksum = "5a33c1b55807fbed163481b5ba66db4b2fa6cde694a5027be10fb724206c5897" 412 | dependencies = [ 413 | "socket2", 414 | "winapi", 415 | ] 416 | 417 | [[package]] 418 | name = "net2" 419 | version = "0.2.37" 420 | source = "registry+https://github.com/rust-lang/crates.io-index" 421 | checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae" 422 | dependencies = [ 423 | "cfg-if 0.1.10", 424 | "libc", 425 | "winapi", 426 | ] 427 | 428 | [[package]] 429 | name = "ntapi" 430 | version = "0.3.6" 431 | source = "registry+https://github.com/rust-lang/crates.io-index" 432 | checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" 433 | dependencies = [ 434 | "winapi", 435 | ] 436 | 437 | [[package]] 438 | name = "num" 439 | version = "0.1.42" 440 | source = "registry+https://github.com/rust-lang/crates.io-index" 441 | checksum = "4703ad64153382334aa8db57c637364c322d3372e097840c72000dabdcf6156e" 442 | dependencies = [ 443 | "num-bigint", 444 | "num-complex", 445 | "num-integer", 446 | "num-iter", 447 | "num-rational", 448 | "num-traits 0.2.14", 449 | ] 450 | 451 | [[package]] 452 | name = "num-bigint" 453 | version = "0.1.44" 454 | source = "registry+https://github.com/rust-lang/crates.io-index" 455 | checksum = "e63899ad0da84ce718c14936262a41cee2c79c981fc0a0e7c7beb47d5a07e8c1" 456 | dependencies = [ 457 | "num-integer", 458 | "num-traits 0.2.14", 459 | "rand", 460 | "rustc-serialize", 461 | ] 462 | 463 | [[package]] 464 | name = "num-complex" 465 | version = "0.1.43" 466 | source = "registry+https://github.com/rust-lang/crates.io-index" 467 | checksum = "b288631d7878aaf59442cffd36910ea604ecd7745c36054328595114001c9656" 468 | dependencies = [ 469 | "num-traits 0.2.14", 470 | "rustc-serialize", 471 | ] 472 | 473 | [[package]] 474 | name = "num-integer" 475 | version = "0.1.44" 476 | source = "registry+https://github.com/rust-lang/crates.io-index" 477 | checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" 478 | dependencies = [ 479 | "autocfg", 480 | "num-traits 0.2.14", 481 | ] 482 | 483 | [[package]] 484 | name = "num-iter" 485 | version = "0.1.42" 486 | source = "registry+https://github.com/rust-lang/crates.io-index" 487 | checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59" 488 | dependencies = [ 489 | "autocfg", 490 | "num-integer", 491 | "num-traits 0.2.14", 492 | ] 493 | 494 | [[package]] 495 | name = "num-rational" 496 | version = "0.1.42" 497 | source = "registry+https://github.com/rust-lang/crates.io-index" 498 | checksum = "ee314c74bd753fc86b4780aa9475da469155f3848473a261d2d18e35245a784e" 499 | dependencies = [ 500 | "num-bigint", 501 | "num-integer", 502 | "num-traits 0.2.14", 503 | "rustc-serialize", 504 | ] 505 | 506 | [[package]] 507 | name = "num-traits" 508 | version = "0.1.43" 509 | source = "registry+https://github.com/rust-lang/crates.io-index" 510 | checksum = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" 511 | dependencies = [ 512 | "num-traits 0.2.14", 513 | ] 514 | 515 | [[package]] 516 | name = "num-traits" 517 | version = "0.2.14" 518 | source = "registry+https://github.com/rust-lang/crates.io-index" 519 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" 520 | dependencies = [ 521 | "autocfg", 522 | ] 523 | 524 | [[package]] 525 | name = "num_cpus" 526 | version = "1.14.0" 527 | source = "registry+https://github.com/rust-lang/crates.io-index" 528 | checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" 529 | dependencies = [ 530 | "hermit-abi", 531 | "libc", 532 | ] 533 | 534 | [[package]] 535 | name = "num_enum" 536 | version = "0.5.1" 537 | source = "registry+https://github.com/rust-lang/crates.io-index" 538 | checksum = "226b45a5c2ac4dd696ed30fa6b94b057ad909c7b7fc2e0d0808192bced894066" 539 | dependencies = [ 540 | "derivative", 541 | "num_enum_derive", 542 | ] 543 | 544 | [[package]] 545 | name = "num_enum_derive" 546 | version = "0.5.1" 547 | source = "registry+https://github.com/rust-lang/crates.io-index" 548 | checksum = "1c0fd9eba1d5db0994a239e09c1be402d35622277e35468ba891aa5e3188ce7e" 549 | dependencies = [ 550 | "proc-macro-crate", 551 | "proc-macro2", 552 | "quote", 553 | "syn", 554 | ] 555 | 556 | [[package]] 557 | name = "parking_lot" 558 | version = "0.11.1" 559 | source = "registry+https://github.com/rust-lang/crates.io-index" 560 | checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" 561 | dependencies = [ 562 | "instant", 563 | "lock_api", 564 | "parking_lot_core", 565 | ] 566 | 567 | [[package]] 568 | name = "parking_lot_core" 569 | version = "0.8.3" 570 | source = "registry+https://github.com/rust-lang/crates.io-index" 571 | checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018" 572 | dependencies = [ 573 | "cfg-if 1.0.0", 574 | "instant", 575 | "libc", 576 | "redox_syscall", 577 | "smallvec", 578 | "winapi", 579 | ] 580 | 581 | [[package]] 582 | name = "pkg-config" 583 | version = "0.3.19" 584 | source = "registry+https://github.com/rust-lang/crates.io-index" 585 | checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" 586 | 587 | [[package]] 588 | name = "proc-macro-crate" 589 | version = "0.1.5" 590 | source = "registry+https://github.com/rust-lang/crates.io-index" 591 | checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" 592 | dependencies = [ 593 | "toml", 594 | ] 595 | 596 | [[package]] 597 | name = "proc-macro2" 598 | version = "1.0.24" 599 | source = "registry+https://github.com/rust-lang/crates.io-index" 600 | checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" 601 | dependencies = [ 602 | "unicode-xid", 603 | ] 604 | 605 | [[package]] 606 | name = "quote" 607 | version = "1.0.8" 608 | source = "registry+https://github.com/rust-lang/crates.io-index" 609 | checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df" 610 | dependencies = [ 611 | "proc-macro2", 612 | ] 613 | 614 | [[package]] 615 | name = "rand" 616 | version = "0.4.6" 617 | source = "registry+https://github.com/rust-lang/crates.io-index" 618 | checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" 619 | dependencies = [ 620 | "fuchsia-cprng", 621 | "libc", 622 | "rand_core 0.3.1", 623 | "rdrand", 624 | "winapi", 625 | ] 626 | 627 | [[package]] 628 | name = "rand_core" 629 | version = "0.3.1" 630 | source = "registry+https://github.com/rust-lang/crates.io-index" 631 | checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" 632 | dependencies = [ 633 | "rand_core 0.4.2", 634 | ] 635 | 636 | [[package]] 637 | name = "rand_core" 638 | version = "0.4.2" 639 | source = "registry+https://github.com/rust-lang/crates.io-index" 640 | checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" 641 | 642 | [[package]] 643 | name = "rdrand" 644 | version = "0.4.0" 645 | source = "registry+https://github.com/rust-lang/crates.io-index" 646 | checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" 647 | dependencies = [ 648 | "rand_core 0.3.1", 649 | ] 650 | 651 | [[package]] 652 | name = "redox_syscall" 653 | version = "0.2.5" 654 | source = "registry+https://github.com/rust-lang/crates.io-index" 655 | checksum = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9" 656 | dependencies = [ 657 | "bitflags", 658 | ] 659 | 660 | [[package]] 661 | name = "regex" 662 | version = "0.2.11" 663 | source = "registry+https://github.com/rust-lang/crates.io-index" 664 | checksum = "9329abc99e39129fcceabd24cf5d85b4671ef7c29c50e972bc5afe32438ec384" 665 | dependencies = [ 666 | "aho-corasick", 667 | "memchr", 668 | "regex-syntax", 669 | "thread_local", 670 | "utf8-ranges", 671 | ] 672 | 673 | [[package]] 674 | name = "regex-syntax" 675 | version = "0.5.6" 676 | source = "registry+https://github.com/rust-lang/crates.io-index" 677 | checksum = "7d707a4fa2637f2dca2ef9fd02225ec7661fe01a53623c1e6515b6916511f7a7" 678 | dependencies = [ 679 | "ucd-util", 680 | ] 681 | 682 | [[package]] 683 | name = "rustc-serialize" 684 | version = "0.3.24" 685 | source = "registry+https://github.com/rust-lang/crates.io-index" 686 | checksum = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" 687 | 688 | [[package]] 689 | name = "scopeguard" 690 | version = "1.1.0" 691 | source = "registry+https://github.com/rust-lang/crates.io-index" 692 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 693 | 694 | [[package]] 695 | name = "serde" 696 | version = "1.0.123" 697 | source = "registry+https://github.com/rust-lang/crates.io-index" 698 | checksum = "92d5161132722baa40d802cc70b15262b98258453e85e5d1d365c757c73869ae" 699 | dependencies = [ 700 | "serde_derive", 701 | ] 702 | 703 | [[package]] 704 | name = "serde_derive" 705 | version = "1.0.123" 706 | source = "registry+https://github.com/rust-lang/crates.io-index" 707 | checksum = "9391c295d64fc0abb2c556bad848f33cb8296276b1ad2677d1ae1ace4f258f31" 708 | dependencies = [ 709 | "proc-macro2", 710 | "quote", 711 | "syn", 712 | ] 713 | 714 | [[package]] 715 | name = "signal-hook" 716 | version = "0.1.17" 717 | source = "registry+https://github.com/rust-lang/crates.io-index" 718 | checksum = "7e31d442c16f047a671b5a71e2161d6e68814012b7f5379d269ebd915fac2729" 719 | dependencies = [ 720 | "libc", 721 | "mio", 722 | "signal-hook-registry", 723 | ] 724 | 725 | [[package]] 726 | name = "signal-hook-registry" 727 | version = "1.3.0" 728 | source = "registry+https://github.com/rust-lang/crates.io-index" 729 | checksum = "16f1d0fef1604ba8f7a073c7e701f213e056707210e9020af4528e0101ce11a6" 730 | dependencies = [ 731 | "libc", 732 | ] 733 | 734 | [[package]] 735 | name = "smallvec" 736 | version = "1.6.1" 737 | source = "registry+https://github.com/rust-lang/crates.io-index" 738 | checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" 739 | 740 | [[package]] 741 | name = "socket2" 742 | version = "0.3.19" 743 | source = "registry+https://github.com/rust-lang/crates.io-index" 744 | checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e" 745 | dependencies = [ 746 | "cfg-if 1.0.0", 747 | "libc", 748 | "winapi", 749 | ] 750 | 751 | [[package]] 752 | name = "strsim" 753 | version = "0.8.0" 754 | source = "registry+https://github.com/rust-lang/crates.io-index" 755 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 756 | 757 | [[package]] 758 | name = "syn" 759 | version = "1.0.60" 760 | source = "registry+https://github.com/rust-lang/crates.io-index" 761 | checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081" 762 | dependencies = [ 763 | "proc-macro2", 764 | "quote", 765 | "unicode-xid", 766 | ] 767 | 768 | [[package]] 769 | name = "textwrap" 770 | version = "0.11.0" 771 | source = "registry+https://github.com/rust-lang/crates.io-index" 772 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 773 | dependencies = [ 774 | "unicode-width", 775 | ] 776 | 777 | [[package]] 778 | name = "thread_local" 779 | version = "0.3.6" 780 | source = "registry+https://github.com/rust-lang/crates.io-index" 781 | checksum = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" 782 | dependencies = [ 783 | "lazy_static", 784 | ] 785 | 786 | [[package]] 787 | name = "time" 788 | version = "0.1.44" 789 | source = "registry+https://github.com/rust-lang/crates.io-index" 790 | checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" 791 | dependencies = [ 792 | "libc", 793 | "wasi", 794 | "winapi", 795 | ] 796 | 797 | [[package]] 798 | name = "toml" 799 | version = "0.5.8" 800 | source = "registry+https://github.com/rust-lang/crates.io-index" 801 | checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" 802 | dependencies = [ 803 | "serde", 804 | ] 805 | 806 | [[package]] 807 | name = "tui" 808 | version = "0.14.0" 809 | source = "registry+https://github.com/rust-lang/crates.io-index" 810 | checksum = "9ced152a8e9295a5b168adc254074525c17ac4a83c90b2716274cc38118bddc9" 811 | dependencies = [ 812 | "bitflags", 813 | "cassowary", 814 | "crossterm 0.18.2", 815 | "unicode-segmentation", 816 | "unicode-width", 817 | ] 818 | 819 | [[package]] 820 | name = "ucd-util" 821 | version = "0.1.8" 822 | source = "registry+https://github.com/rust-lang/crates.io-index" 823 | checksum = "c85f514e095d348c279b1e5cd76795082cf15bd59b93207832abe0b1d8fed236" 824 | 825 | [[package]] 826 | name = "unicode-segmentation" 827 | version = "1.7.1" 828 | source = "registry+https://github.com/rust-lang/crates.io-index" 829 | checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796" 830 | 831 | [[package]] 832 | name = "unicode-width" 833 | version = "0.1.8" 834 | source = "registry+https://github.com/rust-lang/crates.io-index" 835 | checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" 836 | 837 | [[package]] 838 | name = "unicode-xid" 839 | version = "0.2.1" 840 | source = "registry+https://github.com/rust-lang/crates.io-index" 841 | checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" 842 | 843 | [[package]] 844 | name = "utf8-ranges" 845 | version = "1.0.4" 846 | source = "registry+https://github.com/rust-lang/crates.io-index" 847 | checksum = "b4ae116fef2b7fea257ed6440d3cfcff7f190865f170cdad00bb6465bf18ecba" 848 | 849 | [[package]] 850 | name = "vec_map" 851 | version = "0.8.2" 852 | source = "registry+https://github.com/rust-lang/crates.io-index" 853 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 854 | 855 | [[package]] 856 | name = "wasi" 857 | version = "0.10.0+wasi-snapshot-preview1" 858 | source = "registry+https://github.com/rust-lang/crates.io-index" 859 | checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" 860 | 861 | [[package]] 862 | name = "winapi" 863 | version = "0.3.9" 864 | source = "registry+https://github.com/rust-lang/crates.io-index" 865 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 866 | dependencies = [ 867 | "winapi-i686-pc-windows-gnu", 868 | "winapi-x86_64-pc-windows-gnu", 869 | ] 870 | 871 | [[package]] 872 | name = "winapi-i686-pc-windows-gnu" 873 | version = "0.4.0" 874 | source = "registry+https://github.com/rust-lang/crates.io-index" 875 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 876 | 877 | [[package]] 878 | name = "winapi-x86_64-pc-windows-gnu" 879 | version = "0.4.0" 880 | source = "registry+https://github.com/rust-lang/crates.io-index" 881 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 882 | -------------------------------------------------------------------------------- /.idea/workspace.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 23 | 24 | 26 | 27 | 32 | 33 | 34 | 35 | ret 36 | movc 37 | timeout 38 | 39 | 40 | 41 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 84 | 85 | 86 | 88 | 89 | 91 | 92 | 94 | 95 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | --------------------------------------------------------------------------------