Skip to content

Commit e680df4

Browse files
refactor: divide subcommand code
1 parent 52d3964 commit e680df4

4 files changed

Lines changed: 289 additions & 248 deletions

File tree

src/cli/cli.rs

Lines changed: 32 additions & 248 deletions
Original file line numberDiff line numberDiff line change
@@ -1,283 +1,67 @@
1-
use std::net::{TcpStream, Shutdown};
2-
use std::io::{self, Write, BufRead};
3-
use std::sync::mpsc::{Receiver, TryRecvError, Sender};
4-
use std::time::Duration;
5-
use std::{thread, time};
1+
mod server_subcommands;
2+
mod local_subcommands;
3+
mod input_waiter;
64

7-
use rw3d_core::{ constants, commands, packet::WitcherPacket, scriptslog };
5+
use local_subcommands::{LocalSubcommands, handle_local_subcommand};
6+
use server_subcommands::{ServerSubcommands, handle_server_subcommand};
87
use clap::{Parser, Subcommand};
98

109

1110
#[derive(Parser)]
1211
#[clap(name="Rusty Witcher 3 Debugger", version="0.4")]
1312
#[clap(about="A standalone debugging tool for The Witcher 3 written in Rust", long_about=None)]
1413
struct Cli {
15-
/// IPv4 address of the machine on which the game is run
14+
#[clap(flatten)]
15+
options: CliOptions,
16+
17+
#[clap(subcommand)]
18+
command: CliCommands,
19+
}
20+
21+
#[derive(Parser)]
22+
pub(crate) struct CliOptions {
23+
/// IPv4 address of the machine on which the game is run.
1624
#[clap(long, default_value="127.0.0.1")]
1725
ip: String,
1826

19-
/// Exit the program almost immediately after executing the command without listening to responses coming from the game
27+
/// Exit the program almost immediately after executing the command without listening to responses coming from the game.
28+
/// Doesn't apply to scriptslog command.
2029
#[clap(long)]
2130
no_listen: bool,
2231

23-
/// Enable verbose printing of packet contents
32+
/// Enable verbose printing of packet contents.
2433
#[clap(long)]
2534
verbose: bool,
2635

27-
/// Execute command immediately without doing short breaks between info messages beforehand
36+
/// Execute command immediately without doing short breaks between info messages beforehand.
2837
#[clap(long)]
29-
no_info_wait: bool,
38+
no_wait: bool,
3039

3140
/// The maximum amount of milliseconds that program should wait for any game messages until it will automatically exit.
3241
/// This setting is ignored if --no-listen is set.
3342
/// If set to a negative number will wait indefinitely for user's input.
43+
/// Doesn't apply to scriptslog command.
3444
#[clap(long, short, default_value_t=-1)]
3545
response_timeout: i64,
36-
37-
/// Command to use
38-
#[clap(subcommand)]
39-
command: CliCommands,
4046
}
4147

42-
#[derive(Subcommand, PartialEq, Eq)]
48+
#[derive(Subcommand)]
4349
enum CliCommands {
44-
/// Get the root path to game scripts
45-
Rootpath,
46-
/// Reload game scripts
47-
Reload,
48-
/// Run an exec function in the game
49-
Exec{
50-
/// Command to be run in the game
51-
cmd: String
52-
},
53-
/// Get the list of mods installed
54-
Modlist,
55-
/// Get opcode of a script function
56-
Opcode {
57-
/// Name of the function
58-
#[clap(short)]
59-
func_name: String,
60-
/// Name of the class; can be empty
61-
#[clap(short)]
62-
class_name: Option<String>
63-
},
64-
/// Search for config variables
65-
Varlist {
66-
/// Var section to search; if left empty searches all sections
67-
#[clap(short)]
68-
section: Option<String>,
69-
/// Token that should be included in vars; if left empty searches all variables
70-
#[clap(short)]
71-
name: Option<String>
72-
},
73-
/// Sets a config variable
74-
Varset {
75-
/// Variable's section
76-
#[clap(short)]
77-
section: String,
78-
/// Variable's name
79-
#[clap(short)]
80-
name: String,
81-
/// Variable's new value
82-
#[clap(short)]
83-
value: String
84-
},
85-
/// Prints game's script logs onto console
86-
Scriptslog
50+
/// Subcommands that require connection to game's socket and sending messages to it
51+
#[clap(flatten)]
52+
ServerSubcommands(ServerSubcommands),
53+
54+
/// Subcommands that can be executed without connecting to game's socket
55+
#[clap(flatten)]
56+
LocalSubcommands(LocalSubcommands)
8757
}
8858

8959

9060
fn main() {
9161
let cli = Cli::parse();
9262

93-
if cli.command != CliCommands::Scriptslog {
94-
let connection = try_connect(cli.ip.clone(), 5, 1000);
95-
96-
match connection {
97-
Some(mut stream) => {
98-
if !cli.no_info_wait { thread::sleep( time::Duration::from_millis(1000) ) }
99-
println!("Successfully connected to the game!");
100-
101-
if !cli.no_listen {
102-
if !cli.no_info_wait { thread::sleep( time::Duration::from_millis(1000) ) }
103-
println!("Setting up listeners...");
104-
105-
let listeners = commands::listen_all();
106-
for l in &listeners {
107-
stream.write( l.to_bytes().as_slice() ).unwrap();
108-
}
109-
}
110-
111-
112-
if !cli.no_info_wait { thread::sleep( time::Duration::from_millis(1000) ) }
113-
println!("Handling the command...");
114-
115-
let p = match cli.command {
116-
CliCommands::Reload => {
117-
commands::scripts_reload()
118-
}
119-
CliCommands::Exec { cmd } => {
120-
commands::scripts_execute(cmd)
121-
}
122-
CliCommands::Rootpath => {
123-
commands::scripts_root_path()
124-
}
125-
CliCommands::Modlist => {
126-
commands::mod_list()
127-
}
128-
CliCommands::Opcode { func_name, class_name } => {
129-
commands::opcode(func_name, class_name)
130-
}
131-
CliCommands::Varlist { section, name } => {
132-
commands::var_list(section, name)
133-
}
134-
CliCommands::Varset { section, name, value } => {
135-
commands::var_set(section, name, value)
136-
}
137-
//FIXME code will need a makeover because of how scriptslog command makes the program behave differently
138-
// this is a temporary measure
139-
CliCommands::Scriptslog => panic!(),
140-
};
141-
142-
stream.write( p.to_bytes().as_slice() ).unwrap();
143-
144-
145-
if !cli.no_info_wait || !cli.no_listen {
146-
println!("\nYou can press Enter at any moment to exit the program.\n");
147-
if !cli.no_info_wait { thread::sleep( time::Duration::from_millis(3000) ) }
148-
}
149-
150-
if !cli.no_listen {
151-
println!("Game response:\n");
152-
if !cli.no_info_wait { thread::sleep( time::Duration::from_millis(1000) ) }
153-
154-
// Channel to communicate to and from the the reader
155-
let (reader_snd, reader_rcv) = std::sync::mpsc::channel();
156-
157-
// This thread is not expected to finish, so we won't assign a handle to it
158-
// Takes reader_snd so it can communicate to the reader thread to stop execution when user presses Enter
159-
std::thread::spawn(move || input_waiter_thread(reader_snd) );
160-
161-
// This function can either finish by itself by the means of response timeout
162-
// or be stopped by input waiter thread if that one sends him a signal
163-
read_responses(&mut stream, cli.response_timeout, reader_rcv, cli.verbose);
164-
165-
} else {
166-
// Wait a little bit to not finish the connection abruptly
167-
thread::sleep( time::Duration::from_millis(500) );
168-
}
169-
170-
if let Err(e) = stream.shutdown(Shutdown::Both) {
171-
println!("{}", e);
172-
}
173-
174-
}
175-
None => {
176-
println!("Failed to connect to the game on address {}", cli.ip);
177-
}
178-
}
179-
180-
} else {
181-
if !cli.no_info_wait { thread::sleep( time::Duration::from_millis(1000) ) }
182-
println!("Handling the command...");
183-
184-
let (logger_snd, logger_rcv) = std::sync::mpsc::channel();
185-
186-
std::thread::spawn(move || input_waiter_thread(logger_snd) );
187-
188-
println!("\nYou can press Enter at any moment to exit the program.\n");
189-
if !cli.no_info_wait { thread::sleep( time::Duration::from_millis(3000) ) }
190-
191-
if let Some(err) = scriptslog::read_from_scriptslog(|s| print!("{}", s), 1000, logger_rcv) {
192-
println!("{}", err);
193-
}
194-
}
195-
}
196-
197-
198-
199-
fn try_connect(ip: String, max_tries: u8, tries_delay_ms: u64) -> Option<TcpStream> {
200-
let mut tries = max_tries;
201-
202-
while tries > 0 {
203-
println!("Connecting to the game...");
204-
205-
match TcpStream::connect(ip.clone() + ":" + constants::GAME_PORT) {
206-
Ok(conn) => {
207-
return Some(conn);
208-
}
209-
Err(e) => {
210-
println!("{}", e);
211-
}
212-
}
213-
214-
tries -= 1;
215-
thread::sleep( time::Duration::from_millis(tries_delay_ms) );
216-
}
217-
218-
None
219-
}
220-
221-
fn input_waiter_thread(sender: Sender<()>) {
222-
let mut line = String::new();
223-
io::stdin().lock().read_line(&mut line).unwrap();
224-
sender.send(()).unwrap();
225-
}
226-
227-
fn read_responses(stream: &mut TcpStream, response_timeout: i64, cancel_token: Receiver<()>, verbose_print: bool ) {
228-
let mut peek_buffer = [0u8;6];
229-
let mut packet_available: bool;
230-
let mut response_wait_elapsed: i64 = 0;
231-
232-
const READ_TIMEOUT: i64 = 1000;
233-
// Timeout is set so that the peek operation won't block the thread indefinitely after it runs out of data to read
234-
stream.set_read_timeout( Some(Duration::from_millis(READ_TIMEOUT as u64)) ).unwrap();
235-
236-
loop {
237-
// test if the thread has been ordered to stop
238-
match cancel_token.try_recv() {
239-
Ok(_) | Err(TryRecvError::Disconnected) => {
240-
break;
241-
}
242-
Err(TryRecvError::Empty) => {}
243-
}
244-
245-
// Test if there are packets available to be read from stream
246-
// This can block up to the amount specified with set_read_timeout
247-
match stream.peek(&mut peek_buffer) {
248-
Ok(size) => {
249-
packet_available = size > 0;
250-
}
251-
Err(_) => {
252-
packet_available = false;
253-
}
254-
}
255-
256-
if packet_available {
257-
match WitcherPacket::from_stream(stream) {
258-
Ok(packet) => {
259-
if verbose_print {
260-
println!("{:?}", packet);
261-
} else {
262-
println!("{}", packet);
263-
}
264-
}
265-
Err(e) => {
266-
println!("{}", e);
267-
break;
268-
}
269-
}
270-
271-
response_wait_elapsed = 0;
272-
273-
} else {
274-
// if not available it means peek probably waited TIMEOUT millis before it returned
275-
response_wait_elapsed += READ_TIMEOUT;
276-
277-
if response_timeout >= 0 && response_wait_elapsed >= response_timeout {
278-
println!("\nGame response timeout reached.");
279-
break;
280-
}
281-
}
63+
match cli.command {
64+
CliCommands::ServerSubcommands(c) => handle_server_subcommand(c, cli.options),
65+
CliCommands::LocalSubcommands(c) => handle_local_subcommand(c, cli.options),
28266
}
28367
}

src/cli/input_waiter.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
use std::{sync::mpsc::Sender, io::{self, BufRead}};
2+
3+
/// Waits for user to press Enter and then sends the signal using sender
4+
pub(crate) fn input_waiter(sender: Sender<()>) {
5+
let mut line = String::new();
6+
io::stdin().lock().read_line(&mut line).unwrap();
7+
sender.send(()).unwrap();
8+
}

src/cli/local_subcommands.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
use std::{thread, time::Duration};
2+
3+
use clap::Subcommand;
4+
5+
use crate::{input_waiter::input_waiter, CliOptions};
6+
7+
/// Subcommands that can be executed without connecting to game's socket
8+
#[derive(Subcommand)]
9+
pub(crate) enum LocalSubcommands {
10+
/// Prints game's script logs onto console
11+
Scriptslog
12+
}
13+
14+
pub(crate) fn handle_local_subcommand( cmd: LocalSubcommands, options: CliOptions ) {
15+
if !options.no_wait { thread::sleep( Duration::from_millis(1000) ) }
16+
println!("Handling the command...");
17+
18+
let (logger_snd, logger_rcv) = std::sync::mpsc::channel();
19+
20+
std::thread::spawn(move || input_waiter(logger_snd) );
21+
22+
println!("\nYou can press Enter at any moment to exit the program.\n");
23+
if !options.no_wait { thread::sleep( Duration::from_millis(3000) ) }
24+
25+
match cmd {
26+
LocalSubcommands::Scriptslog => {
27+
if let Some(err) = rw3d_core::scriptslog::read_from_scriptslog(|s| print!("{}", s), 1000, logger_rcv) {
28+
println!("{}", err);
29+
}
30+
}
31+
}
32+
}

0 commit comments

Comments
 (0)