├── .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 |
4 |
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 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
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 | 
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 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
32 |
33 |
34 |
35 | ret
36 | movc
37 | timeout
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
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 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 | 1612817187241
271 |
272 |
273 | 1612817187241
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
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 |
--------------------------------------------------------------------------------