Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

High DPI-aware Tarmac #31

Merged
merged 7 commits into from
May 12, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 2 additions & 0 deletions examples/03-high-dpi/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# High DPI Example
This is an example demonstrating how Tarmac detects high DPI variants of assets and automatically generates code to pick the correct variant.
Binary file added examples/03-high-dpi/assets/hello.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/03-high-dpi/assets/hello@2x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/03-high-dpi/assets/hello@3x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions examples/03-high-dpi/src/assets.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
-- This file was @generated by Tarmac. It is not intended for manual editing.
return {
assets = {
LPGhatguy marked this conversation as resolved.
Show resolved Hide resolved
hello = "rbxassetid://4967166732",
["hello@2x"] = "rbxassetid://4967220902",
["hello@3x"] = "rbxassetid://4967220945",
},
}
14 changes: 14 additions & 0 deletions examples/03-high-dpi/tarmac-manifest.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[inputs."assets/hello.png"]
hash = "55ef7fd70ec96da73fa0f09bf530605961cce1a654f02684a1a0b0a6234b06cb"
id = 4967166732
packable = false

[inputs."assets/hello@2x.png"]
hash = "02845c418d2c3abe798d1babeecc2e124dddc4765a840cd03d171f32351be3af"
id = 4967220902
packable = false

[inputs."assets/hello@3x.png"]
hash = "1d075da844e76c9e3ec924c1a7fa8efaedbf35787038063664ef939470d7f57c"
id = 4967220945
packable = false
6 changes: 6 additions & 0 deletions examples/03-high-dpi/tarmac.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
name = "03-high-dpi"

[[inputs]]
glob = "assets/**/*.png"
codegen = true
codegen-path = "src/assets.lua"
6 changes: 0 additions & 6 deletions src/asset_name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,6 @@ impl AssetName {
AssetName(displayed.into())
}

/// Spritesheets don't have any canonical name provided by Tarmac's inputs;
/// when we upload them, we want to give them a simple dummy name
pub fn spritesheet() -> Self {
AssetName("spritesheet.png".into())
}

#[cfg(test)]
pub(crate) fn new<S: AsRef<str>>(inner: S) -> Self {
Self(inner.as_ref().into())
Expand Down
115 changes: 77 additions & 38 deletions src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
//! Tarmac uses a small Lua AST to build up generated code.

use std::{
collections::BTreeMap,
collections::{BTreeMap, HashMap},
io::{self, Write},
path::{self, Path},
};
Expand All @@ -27,22 +27,33 @@ pub fn perform_codegen(output_path: Option<&Path>, inputs: &[&SyncInput]) -> io:
}
}

/// Tree used to track and group inputs hierarchically, before turning them into
/// Lua tables.
enum GroupedItem<'a> {
Folder {
children_by_name: BTreeMap<String, GroupedItem<'a>>,
},
InputGroup {
inputs_by_dpi_scale: HashMap<u32, &'a SyncInput>,
},
}

/// Perform codegen for a group of inputs who have `codegen_path` defined.
///
/// We'll build up a Lua file containing nested tables that match the structure
/// of the input's path with its base path stripped away.
fn codegen_grouped(output_path: &Path, inputs: &[&SyncInput]) -> io::Result<()> {
/// Represents the tree of inputs as we're discovering them.
enum Item<'a> {
Folder(BTreeMap<&'a str, Item<'a>>),
Input(&'a SyncInput),
}

let mut root_folder: BTreeMap<&str, Item<'_>> = BTreeMap::new();
let mut root_folder: BTreeMap<String, GroupedItem<'_>> = BTreeMap::new();

// First, collect all of the inputs and group them together into a tree
// according to their relative paths.
for input in inputs {
for &input in inputs {
// Not all inputs will be marked for codegen. We can eliminate those
// right away.
if !input.config.codegen {
continue;
}

// If we can't construct a relative path, there isn't a sensible name
// that we can use to refer to this input.
let relative_path = input
Expand All @@ -65,69 +76,97 @@ fn codegen_grouped(output_path: &Path, inputs: &[&SyncInput]) -> io::Result<()>

// Navigate down the tree, creating any folder entries that don't exist
// yet.
//
// This is basically an in-memory `mkdir -p` followed by `touch`.
let mut current_dir = &mut root_folder;
for (i, segment) in segments.iter().enumerate() {
if i == segments.len() - 1 {
// We assume that the last segment of a path must be a file.

let name = segment.file_stem().unwrap().to_str().unwrap();
current_dir.insert(name, Item::Input(input));
let name = &input.stem_name;

let input_group = match current_dir.get_mut(name) {
Some(existing) => existing,
None => {
let input_group = GroupedItem::InputGroup {
inputs_by_dpi_scale: HashMap::new(),
};
current_dir.insert(name.to_owned(), input_group);
current_dir.get_mut(name).unwrap()
}
};

if let GroupedItem::InputGroup {
inputs_by_dpi_scale,
} = input_group
{
inputs_by_dpi_scale.insert(input.dpi_scale, input);
} else {
unreachable!();
}
} else {
let name = segment.to_str().unwrap();
let name = segment.to_str().unwrap().to_owned();
let next_entry = current_dir
.entry(name)
.or_insert_with(|| Item::Folder(BTreeMap::new()));
.or_insert_with(|| GroupedItem::Folder {
children_by_name: BTreeMap::new(),
});

match next_entry {
Item::Folder(next_dir) => {
current_dir = next_dir;
}
Item::Input(_) => {
log::error!(
"A path tried to traverse through a folder as if it were a file: {}",
input.path.display()
);
log::error!("The path segment '{}' is a file because of previous inputs, not a file.", name);
break;
}
if let GroupedItem::Folder { children_by_name } = next_entry {
current_dir = children_by_name;
} else {
unreachable!();
}
}
}
}

fn build_item(item: &Item<'_>) -> Option<Expression> {
fn build_item(item: &GroupedItem<'_>) -> Option<Expression> {
match item {
Item::Folder(children) => {
let entries = children
GroupedItem::Folder { children_by_name } => {
let entries = children_by_name
.iter()
.filter_map(|(&name, child)| build_item(child).map(|item| (name.into(), item)))
.filter_map(|(name, child)| build_item(child).map(|item| (name.into(), item)))
.collect();

Some(Expression::table(entries))
}
Item::Input(input) => {
if input.config.codegen {
GroupedItem::InputGroup {
inputs_by_dpi_scale,
} => {
if inputs_by_dpi_scale.len() == 1 {
// If there is exactly one input in this group, we can
// generate code knowing that there are no high DPI variants
// to choose from.

let input = inputs_by_dpi_scale.values().next().unwrap();

if let Some(id) = input.id {
if let Some(slice) = input.slice {
let template = UrlAndSliceTemplate { id, slice };

return Some(template.to_lua());
Some(template.to_lua())
} else {
let template = AssetUrlTemplate { id };

return Some(template.to_lua());
Some(template.to_lua())
}
} else {
None
}
}
} else {
// In this case, we have the same asset in multiple
// different DPI scales. We can generate code to pick
// between them at runtime.

None
unimplemented!()
}
}
}
}

let root_item = build_item(&Item::Folder(root_folder)).unwrap();
let root_item = build_item(&GroupedItem::Folder {
children_by_name: root_folder,
})
.unwrap();
let ast = Statement::Return(root_item);

let mut file = File::create(output_path)?;
Expand Down
12 changes: 8 additions & 4 deletions src/commands/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use crate::{
auth_cookie::get_auth_cookie,
codegen::perform_codegen,
data::{Config, ConfigError, ImageSlice, InputManifest, Manifest, ManifestError, SyncInput},
dpi_scale::dpi_scale_for_path,
dpi_scale,
image::Image,
options::{GlobalOptions, SyncOptions, SyncTarget},
roblox_web_api::{RobloxApiClient, RobloxApiError},
Expand Down Expand Up @@ -241,6 +241,8 @@ impl SyncSession {
let name = AssetName::from_paths(config_path, &path);
log::trace!("Found input {}", name);

let path_info = dpi_scale::extract_path_info(&path);

let contents = fs::read(&path)?;
let hash = generate_asset_hash(&contents);

Expand All @@ -255,8 +257,10 @@ impl SyncSession {
name.clone(),
SyncInput {
name,
stem_name: path_info.file_stem,
path,
config: input_config.clone(),
dpi_scale: path_info.dpi_scale,
contents,
hash,
id,
Expand Down Expand Up @@ -291,7 +295,7 @@ impl SyncSession {

let kind = InputKind {
packable: input.config.packable,
dpi_scale: dpi_scale_for_path(&input.path),
dpi_scale: input.dpi_scale,
};

let input_group = compatible_input_groups.entry(kind).or_insert_with(Vec::new);
Expand Down Expand Up @@ -422,7 +426,7 @@ impl SyncSession {
let hash = generate_asset_hash(&encoded_image);

let upload_data = UploadInfo {
name: AssetName::spritesheet(),
name: "spritesheet".to_owned(),
contents: encoded_image,
hash: hash.clone(),
};
Expand All @@ -448,7 +452,7 @@ impl SyncSession {
let input = self.inputs.get_mut(input_name).unwrap();

let upload_data = UploadInfo {
name: input_name.clone(),
name: input.stem_name.clone(),
contents: input.contents.clone(),
hash: input.hash.clone(),
};
Expand Down
8 changes: 8 additions & 0 deletions src/data/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,22 @@ use crate::{
/// results of network I/O, and from the previous Tarmac manifest file.
#[derive(Debug)]
pub struct SyncInput {
/// A unique name for this asset in the project.
pub name: AssetName,

/// A non-unique name for this asset, excluding file extension and DPI scale
/// identifier, if present.
pub stem_name: String,

/// The path on disk to the file this input originated from.
pub path: PathBuf,

/// The configuration that applied to this input when it was discovered.
pub config: InputConfig,

/// The DPI scale of this input, if it makes sense for this input type.
pub dpi_scale: u32,

/// The contents of the file this input originated from.
pub contents: Vec<u8>,

Expand Down
Loading