Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions dmsrc/iconforge.dm
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,21 @@
#define rustg_iconforge_generate(file_path, spritesheet_name, sprites, hash_icons, generate_dmi, flatten) RUSTG_CALL(RUST_G, "iconforge_generate")(file_path, spritesheet_name, sprites, "[hash_icons]", "[generate_dmi]", "[flatten]")
/// Returns a job_id for use with rustg_iconforge_check()
#define rustg_iconforge_generate_async(file_path, spritesheet_name, sprites, hash_icons, generate_dmi, flatten) RUSTG_CALL(RUST_G, "iconforge_generate_async")(file_path, spritesheet_name, sprites, "[hash_icons]", "[generate_dmi]", "[flatten]")
/// Creates a single DMI or PNG using 'sprites' as a list of icon states / images.
/// This function is intended for generating icons with only a few states that have little in common with each other, and only one size.
/// For icons with a large number of states, potentially variable sizes, that re-use sets of transforms more than once, or that benefit from caching, use rustg_iconforge_generate.
/// sprites - follows the same format as rustg_iconforge_generate.
/// file_path - the full relative path at which the PNG or DMI will be written. It must be a full filepath such as tmp/my_icon.dmi or my_icon.png
/// flatten - boolean (0 or 1) determines if the DMI output will be flattened to a single frame/dir if unscoped (null/0 dir or frame values).
///
/// Returns a HeadlessResult, decoded to a BYOND list (always, it's not possible for this to panic unless rustg itself has an issue) containing the following fields:
/// list(
/// "file_path" = "tmp/my_icon.dmi" // [whatever you input returned back to you, null if there was a fatal error]
/// "width" = 32 // the width, which is determined by the first entry of 'sprites', null if there was a fatal error
/// "height" = 32 // the height, which is determined by the first entry of 'sprites', null if there was a fatal error
/// "error" = "[A string, null if there were no errors.]"
/// )
#define rustg_iconforge_generate_headless(file_path, sprites, flatten) json_decode(RUSTG_CALL(RUST_G, "iconforge_generate_headless")(file_path, sprites, "[flatten]"))
/// Returns the status of an async job_id, or its result if it is completed. See RUSTG_JOB DEFINEs.
#define rustg_iconforge_check(job_id) RUSTG_CALL(RUST_G, "iconforge_check")("[job_id]")
/// Clears all cached DMIs and images, freeing up memory.
Expand Down
6 changes: 3 additions & 3 deletions src/byond.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,12 +123,12 @@ pub fn set_panic_hook() {
#[allow(dead_code)] // Used depending on feature set
/// Utility for BYOND functions to catch panic unwinds safely and return a Result<String, Error>, as expected.
/// Usage: catch_panic(|| internal_safe_function(arguments))
pub fn catch_panic<F>(f: F) -> Result<String, Error>
pub fn catch_panic<F, R>(f: F) -> Result<R, Error>
where
F: FnOnce() -> Result<String, Error> + std::panic::UnwindSafe,
F: FnOnce() -> R + std::panic::UnwindSafe,
{
match std::panic::catch_unwind(f) {
Ok(o) => o,
Ok(o) => Ok(o),
Err(e) => {
let message: Option<String> = e
.downcast_ref::<&'static str>()
Expand Down
65 changes: 56 additions & 9 deletions src/iconforge/byond.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use super::{gags, image_cache, spritesheet};
use crate::{byond::catch_panic, jobs};
use crate::{byond::catch_panic, iconforge::spritesheet::HeadlessResult, jobs};
use tracy_full::frame;

byond_fn!(fn iconforge_generate(file_path, spritesheet_name, sprites, hash_icons, generate_dmi, flatten) {
Expand All @@ -10,7 +10,10 @@ byond_fn!(fn iconforge_generate(file_path, spritesheet_name, sprites, hash_icons
let generate_dmi = generate_dmi.to_owned();
let flatten = flatten.to_owned();
let result = Some(match catch_panic(|| spritesheet::generate_spritesheet(&file_path, &spritesheet_name, &sprites, &hash_icons, &generate_dmi, &flatten)) {
Ok(o) => o.to_string(),
Ok(o) => match o {
Ok(o) => o,
Err(e) => e.to_string()
},
Err(e) => e.to_string()
});
frame!();
Expand All @@ -26,14 +29,40 @@ byond_fn!(fn iconforge_generate_async(file_path, spritesheet_name, sprites, hash
let flatten = flatten.to_owned();
Some(jobs::start(move || {
let result = match catch_panic(|| spritesheet::generate_spritesheet(&file_path, &spritesheet_name, &sprites, &hash_icons, &generate_dmi, &flatten)) {
Ok(o) => o.to_string(),
Ok(o) => match o {
Ok(o) => o,
Err(e) => e.to_string()
},
Err(e) => e.to_string()
};
frame!();
result
}))
});

byond_fn!(fn iconforge_generate_headless(file_path, sprites, flatten) {
let file_path = file_path.to_owned();
let sprites = sprites.to_owned();
let flatten = flatten.to_owned();
let result = Some(match catch_panic::<_, HeadlessResult>(|| spritesheet::generate_headless(&file_path, &sprites, &flatten)) {
Ok(o) => match serde_json::to_string::<HeadlessResult>(&o) {
Ok(o) => o,
Err(_) => String::from("{\"error\":\"Serde serialization error\"}") // nigh impossible but whatever
},
Err(e) => match serde_json::to_string::<HeadlessResult>(&HeadlessResult {
file_path: None,
width: None,
height: None,
error: Some(e.to_string()),
}) {
Ok(o) => o,
Err(_) => String::from("{\"error\":\"Serde serialization error\"}")
}
});
frame!();
result
});

byond_fn!(fn iconforge_check(id) {
Some(jobs::check(id))
});
Expand All @@ -51,7 +80,10 @@ byond_fn!(fn iconforge_cache_valid(input_hash, dmi_hashes, sprites) {
let dmi_hashes = dmi_hashes.to_owned();
let sprites = sprites.to_owned();
let result = Some(match catch_panic(|| spritesheet::cache_valid(&input_hash, &dmi_hashes, &sprites)) {
Ok(o) => o.to_string(),
Ok(o) => match o {
Ok(o) => o,
Err(e) => e.to_string()
},
Err(e) => e.to_string()
});
frame!();
Expand All @@ -64,7 +96,10 @@ byond_fn!(fn iconforge_cache_valid_async(input_hash, dmi_hashes, sprites) {
let sprites = sprites.to_owned();
let result = Some(jobs::start(move || {
match catch_panic(|| spritesheet::cache_valid(&input_hash, &dmi_hashes, &sprites)) {
Ok(o) => o.to_string(),
Ok(o) => match o {
Ok(o) => o,
Err(e) => e.to_string()
},
Err(e) => e.to_string()
}
}));
Expand All @@ -77,7 +112,10 @@ byond_fn!(fn iconforge_load_gags_config(config_path, config_json, config_icon_pa
let config_json = config_json.to_owned();
let config_icon_path = config_icon_path.to_owned();
let result = Some(match catch_panic(|| gags::load_gags_config(&config_path, &config_json, &config_icon_path)) {
Ok(o) => o.to_string(),
Ok(o) => match o {
Ok(o) => o,
Err(e) => e.to_string()
},
Err(e) => e.to_string()
});
frame!();
Expand All @@ -90,7 +128,10 @@ byond_fn!(fn iconforge_load_gags_config_async(config_path, config_json, config_i
let config_icon_path = config_icon_path.to_owned();
Some(jobs::start(move || {
let result = match catch_panic(|| gags::load_gags_config(&config_path, &config_json, &config_icon_path)) {
Ok(o) => o.to_string(),
Ok(o) => match o {
Ok(o) => o,
Err(e) => e.to_string()
},
Err(e) => e.to_string()
};
frame!();
Expand All @@ -103,7 +144,10 @@ byond_fn!(fn iconforge_gags(config_path, colors, output_dmi_path) {
let colors = colors.to_owned();
let output_dmi_path = output_dmi_path.to_owned();
let result = Some(match catch_panic(|| gags::gags(&config_path, &colors, &output_dmi_path)) {
Ok(o) => o.to_string(),
Ok(o) => match o {
Ok(o) => o,
Err(e) => e.to_string()
},
Err(e) => e.to_string()
});
frame!();
Expand All @@ -116,7 +160,10 @@ byond_fn!(fn iconforge_gags_async(config_path, colors, output_dmi_path) {
let output_dmi_path = output_dmi_path.to_owned();
Some(jobs::start(move || {
let result = match catch_panic(|| gags::gags(&config_path, &colors, &output_dmi_path)) {
Ok(o) => o.to_string(),
Ok(o) => match o {
Ok(o) => o,
Err(e) => e.to_string()
},
Err(e) => e.to_string()
};
frame!();
Expand Down
49 changes: 29 additions & 20 deletions src/iconforge/icon_operations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -553,15 +553,19 @@ pub fn blend_images_other_universal(
.clone()
.unwrap_or(vec![1.0; frames_out as usize]);
let delay_diff = frames_out as i32 - new_delays.len() as i32;
// Extend the number of delays to match frames by copying the first delay
if delay_diff > 0 {
new_delays.extend(vec![
*new_delays.first().unwrap_or(&1.0);
delay_diff as usize
]);
} else if delay_diff < 0 {
// sometimes DMIs can contain more delays than frames because they retain old data
new_delays = new_delays[0..frames_out as usize].to_vec()
match delay_diff.cmp(&0) {
std::cmp::Ordering::Greater => {
// Extend the number of delays to match frames by copying the first delay
new_delays.extend(vec![
*new_delays.first().unwrap_or(&1.0);
delay_diff as usize
]);
}
std::cmp::Ordering::Less => {
// sometimes DMIs can contain more delays than frames because they retain old data
new_delays = new_delays[0..frames_out as usize].to_vec()
}
_ => {}
}
delay_out = Some(new_delays);
} else {
Expand Down Expand Up @@ -691,15 +695,20 @@ pub fn blend_images_other(
.clone()
.unwrap_or(vec![1.0; base_icon_state.frames as usize]);
let delay_diff = base_icon_state.frames as i32 - new_delays.len() as i32;
// Extend the number of delays to match frames by copying the first delay
if delay_diff > 0 {
new_delays.extend(vec![
*new_delays.first().unwrap_or(&1.0);
delay_diff as usize
]);
} else if delay_diff < 0 {
// sometimes DMIs can contain more delays than frames because they retain old data
new_delays = new_delays[0..base_icon_state.frames as usize].to_vec()

match delay_diff.cmp(&0) {
std::cmp::Ordering::Greater => {
// Extend the number of delays to match frames by copying the first delay
new_delays.extend(vec![
*new_delays.first().unwrap_or(&1.0);
delay_diff as usize
]);
}
std::cmp::Ordering::Less => {
// sometimes DMIs can contain more delays than frames because they retain old data
new_delays = new_delays[0..base_icon_state.frames as usize].to_vec()
}
_ => {}
}
base_icon_state.delay = Some(new_delays);
} else {
Expand Down Expand Up @@ -788,7 +797,7 @@ impl Transform {
frames = new_out.frames;
dirs = new_out.dirs;
delay = new_out.delay;
image_cache::cache_transformed_images(icon, other_image_data);
image_cache::cache_transformed_images(icon, other_image_data, flatten);
}
Transform::Scale { width, height } => {
images = image_data.map_cloned_images(|image| scale(image, *width, *height));
Expand Down Expand Up @@ -888,7 +897,7 @@ impl Transform {
}

/// Applies a list of Transforms to UniversalIconData immediately and sequentially, while handling any errors. Optionally flattens to only the first dir and frame.
fn apply_all_transforms(
pub fn apply_all_transforms(
image_data: Arc<UniversalIconData>,
transforms: &Vec<Transform>,
flatten: bool,
Expand Down
31 changes: 26 additions & 5 deletions src/iconforge/image_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,21 @@ static ICON_STATES: Lazy<
DashMap<UniversalIcon, Arc<UniversalIconData>, BuildHasherDefault<XxHash64>>,
> = Lazy::new(|| DashMap::with_hasher(BuildHasherDefault::<XxHash64>::default()));

pub fn image_cache_contains(icon: &UniversalIcon) -> bool {
ICON_STATES.contains_key(icon)
static ICON_STATES_FLAT: Lazy<
DashMap<UniversalIcon, Arc<UniversalIconData>, BuildHasherDefault<XxHash64>>,
> = Lazy::new(|| DashMap::with_hasher(BuildHasherDefault::<XxHash64>::default()));

pub fn image_cache_contains(icon: &UniversalIcon, flatten: bool) -> bool {
if flatten {
ICON_STATES_FLAT.contains_key(icon)
} else {
ICON_STATES.contains_key(icon)
}
}

pub fn image_cache_clear() {
ICON_STATES.clear();
ICON_STATES_FLAT.clear();
}

impl UniversalIcon {
Expand All @@ -37,7 +46,11 @@ impl UniversalIcon {
zone!("universal_icon_to_image_data");
if cached {
zone!("check_image_cache");
if let Some(entry) = ICON_STATES.get(self) {
if let Some(entry) = if flatten {
ICON_STATES_FLAT.get(self)
} else {
ICON_STATES.get(self)
} {
return Ok((entry.value().to_owned(), true));
}
if must_be_cached {
Expand Down Expand Up @@ -162,9 +175,17 @@ impl UniversalIcon {
}
}

pub fn cache_transformed_images(uni_icon: &UniversalIcon, image_data: Arc<UniversalIconData>) {
pub fn cache_transformed_images(
uni_icon: &UniversalIcon,
image_data: Arc<UniversalIconData>,
flatten: bool,
) {
zone!("cache_transformed_images");
ICON_STATES.insert(uni_icon.to_owned(), image_data.to_owned());
if flatten {
ICON_STATES_FLAT.insert(uni_icon.to_owned(), image_data.to_owned());
} else {
ICON_STATES.insert(uni_icon.to_owned(), image_data.to_owned());
}
}

/* ---- DMI CACHING ---- */
Expand Down
Loading
Loading