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 28 29 30 31 32 33 34 35 36 37 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
#![warn(missing_docs)]
#![doc = include_str!("../README.md")]
use num_enum::TryFromPrimitiveError;
mod channel;
mod message;
pub use channel::Channel;
#[doc(no_inline)]
pub use dfhack_proto::messages::*;
pub use dfhack_proto::stubs::*;
use message::CommandResult;
/// DFHack client, build it with [connect] or [connect_to]
pub type Client = Stubs<Channel>;
/// Connect to Dwarf Fortress using the default settings
///
/// It will try to connect to `127.0.0.1:5000`, DFHack default address.
/// The port can be overriden with `DFHACK_PORT`, which is also taken in account by DFHack.
///
/// For remote connexion, see [connect_to].
///
/// # Examples
///
/// ```no_run
/// use dfhack_remote;
///
/// let mut dfhack = dfhack_remote::connect().unwrap();
/// let df_version = dfhack.core().get_df_version().unwrap();
/// println!("DwarfFortress {}", df_version);
/// ```
pub fn connect() -> Result<Client> {
let connexion = Channel::connect()?;
Ok(Stubs::from(connexion))
}
/// Connect to Dwarf Fortress with a given address
///
/// # Arguments
///
/// * `address` - Address of the DFHack server. By default, DFHack runs of `127.0.0.1:5000`
///
/// # Examples
///
/// ```no_run
/// use dfhack_remote;
/// let mut dfhack = dfhack_remote::connect_to("127.0.0.1:5000").unwrap();
/// let df_version = dfhack.core().get_df_version().unwrap();
/// println!("DwarfFortress {}", df_version);
/// ```
///
pub fn connect_to(address: &str) -> Result<Client> {
let connexion = Channel::connect_to(address)?;
Ok(Stubs::from(connexion))
}
/// Result type emitted by DFHack API calls
pub type Result<T> = std::result::Result<T, Error>;
/// Error type emitted by DFHack API calls
#[derive(thiserror::Error, Debug)]
pub enum Error {
/// A low level connexion error
///
/// This can mean that the address is wrong,
/// that Dwarf Fortress crashed, or a library bug occured.
#[error("communication failure: {0}")]
CommunicationFailure(#[from] std::io::Error),
/// The data exchange did not happen as expected.
///
/// This is likely a bug.
#[error("protocol error: {0}.")]
ProtocolError(String),
/// Protobuf serialization or deserialization error
///
/// This can indicate that updating the generated code
/// is necessary
#[error("protobuf serialization error: {0}.")]
ProtobufError(#[from] protobuf::Error),
/// Failed to bind the method
///
/// This can indicate that updating the generated code
/// is necessary
#[error("failed to bind {0}.")]
FailedToBind(String),
/// DFHack RPC Error
#[error("RPC error: {0}.")]
RpcError(CommandResult),
}
impl From<TryFromPrimitiveError<message::RpcReplyCode>> for Error {
fn from(err: TryFromPrimitiveError<message::RpcReplyCode>) -> Self {
Self::ProtocolError(format!("Unknown DFHackReplyCode : {}", err.number))
}
}
impl From<std::string::FromUtf8Error> for Error {
fn from(err: std::string::FromUtf8Error) -> Self {
Self::ProtocolError(format!("Invalid string error: {}", err))
}
}
#[cfg(test)]
mod tests {
#[ctor::ctor]
fn init() {
env_logger::init();
}
#[cfg(feature = "test-with-df")]
mod withdf {
use std::process::Child;
use std::sync::Mutex;
use rand::Rng;
#[cfg(test)]
lazy_static::lazy_static! {
static ref DF_PROCESS: Mutex<Option<Child>> = Mutex::new(Option::<Child>::None);
}
#[ctor::ctor]
fn init() {
let port = rand::thread_rng().gen_range(49152..65535).to_string();
std::env::set_var("DFHACK_PORT", port);
use std::{path::PathBuf, process::Command};
let df_exe = PathBuf::from(std::env::var("DF_EXE").unwrap());
let df_folder = df_exe.parent().unwrap();
let df = Command::new(&df_exe)
.args(["+load-save", "region1"])
.current_dir(df_folder)
.spawn()
.unwrap();
let mut process_guard = DF_PROCESS.lock().unwrap();
process_guard.replace(df);
}
#[ctor::dtor]
fn exit() {
let mut process_guard = DF_PROCESS.lock().unwrap();
let df = process_guard.take();
if let Some(mut df) = df {
df.kill().unwrap();
}
}
#[test]
fn get_version() {
let mut client = crate::connect().unwrap();
let version = client.core().get_df_version().unwrap();
assert!(version.len() > 0);
}
#[test]
fn pause_unpause() {
let mut client = crate::connect().unwrap();
let initial_pause_status = client.remote_fortress_reader().get_pause_state().unwrap();
client
.remote_fortress_reader()
.set_pause_state(!initial_pause_status)
.unwrap();
let new_pause_status = client.remote_fortress_reader().get_pause_state().unwrap();
assert!(initial_pause_status != new_pause_status);
}
}
}