feat: close stage 0-2 audit groundwork
Remove legacy SDL/OpenGL adapters from the workspace and introduce winit/Vulkan adapter boundaries for the rendered composition root. Add reproducible toolchain and xtask CI coverage for formatting, tests, clippy, docs, policy, deny, acceptance auditing, and hosted OS matrix evidence. Strengthen Stage 1 data contracts with byte-first paths, VFS hardening, structured diagnostics, RsLi writer/edit scaffolding, corpus reporting, and resource error classification. Advance Stage 2 asset preparation by moving mission loading through assets/runtime boundaries, materializing prototype graph data, preserving provenance, and adding inspection/viewer integration. Record the Stage 0-2 audit input, acceptance roadmap, coverage updates, and documentation notes for follow-up evidence.
This commit is contained in:
@@ -7,10 +7,9 @@ repository.workspace = true
|
||||
|
||||
[dependencies]
|
||||
fparkan-corpus = { path = "../../crates/fparkan-corpus" }
|
||||
fparkan-nres = { path = "../../crates/fparkan-nres" }
|
||||
fparkan-prototype = { path = "../../crates/fparkan-prototype" }
|
||||
fparkan-inspection = { path = "../../crates/fparkan-inspection" }
|
||||
fparkan-resource = { path = "../../crates/fparkan-resource" }
|
||||
fparkan-rsli = { path = "../../crates/fparkan-rsli" }
|
||||
fparkan-runtime = { path = "../../crates/fparkan-runtime" }
|
||||
fparkan-vfs = { path = "../../crates/fparkan-vfs" }
|
||||
|
||||
|
||||
@@ -3,9 +3,10 @@
|
||||
//! `FParkan` command-line tools.
|
||||
|
||||
use fparkan_corpus::{discover, render_report_json, report, DiscoverOptions};
|
||||
use fparkan_prototype::{
|
||||
build_prototype_graph_report, extend_graph_report_with_visual_dependencies,
|
||||
};
|
||||
use fparkan_inspection::inspect_archive_file;
|
||||
use fparkan_inspection::ArchiveInspection;
|
||||
use fparkan_assets::extend_graph_report_with_visual_dependencies;
|
||||
use fparkan_prototype::build_prototype_graph_report;
|
||||
use fparkan_resource::{resource_name, CachedResourceRepository};
|
||||
use fparkan_runtime::{
|
||||
create, load_mission, EngineConfig, EngineMode, EngineServices, MissionRequest,
|
||||
@@ -134,7 +135,12 @@ fn inspect_prototype(args: &[String]) -> Result<(), String> {
|
||||
let roots = [resource_name(key.as_bytes())];
|
||||
let (graph, resolved, mut report) =
|
||||
build_prototype_graph_report(&repository, vfs.as_ref(), &roots);
|
||||
extend_graph_report_with_visual_dependencies(&repository, &mut report, &resolved);
|
||||
extend_graph_report_with_visual_dependencies(
|
||||
&repository,
|
||||
&mut report,
|
||||
&graph,
|
||||
&resolved,
|
||||
);
|
||||
println!("{}", prototype_inspect_json(&key, &graph, &report));
|
||||
Ok(())
|
||||
}
|
||||
@@ -202,42 +208,34 @@ fn graph_mission(args: &[String]) -> Result<(), String> {
|
||||
|
||||
fn inspect_archive(args: &[String]) -> Result<(), String> {
|
||||
let path = parse_archive_path(args)?;
|
||||
let bytes = std::fs::read(&path).map_err(|err| format!("{}: {err}", path.display()))?;
|
||||
if bytes.starts_with(b"NRes") {
|
||||
let document = fparkan_nres::decode(
|
||||
Arc::from(bytes.into_boxed_slice()),
|
||||
fparkan_nres::ReadProfile::Compatible,
|
||||
)
|
||||
.map_err(|err| err.to_string())?;
|
||||
println!(
|
||||
"{}",
|
||||
archive_inspect_json(
|
||||
&path.display().to_string(),
|
||||
"NRes",
|
||||
document.entries().len(),
|
||||
Some(document.lookup_order_valid()),
|
||||
)
|
||||
);
|
||||
return Ok(());
|
||||
let inspection = inspect_archive_file(&path, 0).map_err(|err| err.to_string())?;
|
||||
|
||||
match inspection {
|
||||
ArchiveInspection::Nres {
|
||||
entries,
|
||||
lookup_order_valid,
|
||||
..
|
||||
} => {
|
||||
println!(
|
||||
"{}",
|
||||
archive_inspect_json(
|
||||
&path.display().to_string(),
|
||||
"NRes",
|
||||
entries,
|
||||
Some(lookup_order_valid),
|
||||
)
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
ArchiveInspection::Rsli { entries } => {
|
||||
println!(
|
||||
"{}",
|
||||
archive_inspect_json(&path.display().to_string(), "RsLi", entries, None)
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
ArchiveInspection::Unsupported => Err(format!("{}: unsupported archive magic", path.display())),
|
||||
}
|
||||
if bytes.get(0..4) == Some(b"NL\0\x01") {
|
||||
let document = fparkan_rsli::decode(
|
||||
Arc::from(bytes.into_boxed_slice()),
|
||||
fparkan_rsli::ReadProfile::Compatible,
|
||||
)
|
||||
.map_err(|err| err.to_string())?;
|
||||
println!(
|
||||
"{}",
|
||||
archive_inspect_json(
|
||||
&path.display().to_string(),
|
||||
"RsLi",
|
||||
document.entries().len(),
|
||||
None
|
||||
)
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
Err(format!("{}: unsupported archive magic", path.display()))
|
||||
}
|
||||
|
||||
fn archive_inspect_json(
|
||||
@@ -278,7 +276,7 @@ fn json_string(value: &str) -> String {
|
||||
'\r' => out.push_str("\\r"),
|
||||
'\t' => out.push_str("\\t"),
|
||||
c if c.is_control() => {
|
||||
let _ = write!(out, "\\u{:04x}", u32::from(c));
|
||||
let _ = write!(out, "\\u{:04x}", c as u32);
|
||||
}
|
||||
c => out.push(c),
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ repository.workspace = true
|
||||
|
||||
[dependencies]
|
||||
fparkan-render = { path = "../../crates/fparkan-render" }
|
||||
fparkan-platform-winit = { path = "../../adapters/fparkan-platform-winit" }
|
||||
fparkan-render-vulkan = { path = "../../adapters/fparkan-render-vulkan" }
|
||||
fparkan-runtime = { path = "../../crates/fparkan-runtime" }
|
||||
fparkan-vfs = { path = "../../crates/fparkan-vfs" }
|
||||
fparkan-world = { path = "../../crates/fparkan-world" }
|
||||
|
||||
@@ -3,11 +3,14 @@
|
||||
//! `FParkan` rendered game composition root.
|
||||
|
||||
use fparkan_render::{
|
||||
DrawCommand, DrawId, GpuMaterialId, GpuMeshId, IndexRange, RecordingBackend, RenderBackend,
|
||||
DrawCommand, DrawId, GpuMaterialId, GpuMeshId, IndexRange, RenderBackend,
|
||||
RenderCommand, RenderCommandList, RenderPhase,
|
||||
};
|
||||
use fparkan_platform_winit::WinitWindow;
|
||||
use fparkan_render_vulkan::VulkanBackend;
|
||||
use fparkan_runtime::{
|
||||
create, frame, load_mission, EngineConfig, EngineMode, EngineServices, MissionRequest,
|
||||
MissionAssets, loaded_mission_assets,
|
||||
};
|
||||
use fparkan_vfs::DirectoryVfs;
|
||||
use fparkan_world::WorldSnapshot;
|
||||
@@ -47,7 +50,11 @@ fn run(args: &[String]) -> Result<String, String> {
|
||||
)
|
||||
.map_err(|err| err.to_string())?;
|
||||
|
||||
let mut backend = RecordingBackend::default();
|
||||
let mut backend = VulkanBackend::new();
|
||||
let _request = WinitWindow::default_render_request();
|
||||
let window = WinitWindow::synthetic(1280, 720);
|
||||
let _ = window.drawable_size();
|
||||
let _ = window.handle();
|
||||
let mut last_draw_count = 0usize;
|
||||
let mut last_tick = 0u64;
|
||||
let mut last_hash = [0u8; 32];
|
||||
@@ -55,7 +62,8 @@ fn run(args: &[String]) -> Result<String, String> {
|
||||
let result = frame(&mut engine).map_err(|err| err.to_string())?;
|
||||
last_tick = result.snapshot.tick.0;
|
||||
last_hash = result.snapshot.hash.0;
|
||||
let commands = render_snapshot_commands(&result.snapshot);
|
||||
let mission_assets = loaded_mission_assets(&engine);
|
||||
let commands = render_snapshot_commands_with_assets(&result.snapshot, mission_assets);
|
||||
last_draw_count = commands
|
||||
.commands
|
||||
.iter()
|
||||
@@ -66,6 +74,8 @@ fn run(args: &[String]) -> Result<String, String> {
|
||||
.map_err(|err| format!("render backend: {err}"))?;
|
||||
}
|
||||
|
||||
let capture_report = backend.report();
|
||||
|
||||
Ok(format!(
|
||||
"{{\"mission\":{},\"objects\":{},\"frames\":{},\"tick\":{},\"draws\":{},\"captures\":{},\"last_capture_bytes\":{},\"hash\":{}}}",
|
||||
json_string(&args.mission),
|
||||
@@ -73,17 +83,40 @@ fn run(args: &[String]) -> Result<String, String> {
|
||||
args.frames,
|
||||
last_tick,
|
||||
last_draw_count,
|
||||
backend.captures().len(),
|
||||
backend.last_capture().map_or(0, <[u8]>::len),
|
||||
capture_report.submissions,
|
||||
capture_report.last_capture_size,
|
||||
json_hash(&last_hash)
|
||||
))
|
||||
}
|
||||
|
||||
fn render_snapshot_commands(snapshot: &WorldSnapshot) -> RenderCommandList {
|
||||
render_snapshot_commands_with_assets(snapshot, None)
|
||||
}
|
||||
|
||||
fn render_snapshot_commands_with_assets(
|
||||
snapshot: &WorldSnapshot,
|
||||
mission_assets: Option<&MissionAssets>,
|
||||
) -> RenderCommandList {
|
||||
let mut commands = Vec::with_capacity(snapshot.objects.len() + 2);
|
||||
commands.push(RenderCommand::BeginFrame);
|
||||
for (index, handle) in snapshot.objects.iter().enumerate() {
|
||||
let stable_order = u64::from(handle.slot);
|
||||
let prepared = mission_assets.and_then(|assets| {
|
||||
assets
|
||||
.visual_for_object(index)
|
||||
.and_then(|visual_id| assets.visual_by_id(visual_id))
|
||||
});
|
||||
let mesh = if let Some(visual) = prepared {
|
||||
visual.mesh.as_ref().map_or_else(
|
||||
|| GpuMeshId(u64::from(handle.slot) + 1),
|
||||
|_| GpuMeshId(visual.id.raw()),
|
||||
)
|
||||
} else {
|
||||
GpuMeshId(u64::from(handle.slot) + 1)
|
||||
};
|
||||
let material = prepared
|
||||
.and_then(|visual| visual.primary_material_id())
|
||||
.map_or(GpuMaterialId(1), |material_id| GpuMaterialId(material_id.raw()));
|
||||
let draw_id = snapshot
|
||||
.tick
|
||||
.0
|
||||
@@ -93,8 +126,8 @@ fn render_snapshot_commands(snapshot: &WorldSnapshot) -> RenderCommandList {
|
||||
id: DrawId(draw_id),
|
||||
phase: RenderPhase::Opaque,
|
||||
object_id: None,
|
||||
mesh: GpuMeshId(u64::from(handle.slot) + 1),
|
||||
material: GpuMaterialId(1),
|
||||
mesh,
|
||||
material,
|
||||
transform: identity_transform(index_to_f32(index)),
|
||||
range: IndexRange { start: 0, count: 3 },
|
||||
stable_order,
|
||||
@@ -178,7 +211,7 @@ fn json_string(value: &str) -> String {
|
||||
'\t' => out.push_str("\\t"),
|
||||
c if c.is_control() => {
|
||||
use std::fmt::Write as _;
|
||||
let _ = write!(out, "\\u{:04x}", u32::from(c));
|
||||
let _ = write!(out, "\\u{:04x}", c as u32);
|
||||
}
|
||||
c => out.push(c),
|
||||
}
|
||||
|
||||
@@ -6,14 +6,8 @@ license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[dependencies]
|
||||
fparkan-msh = { path = "../../crates/fparkan-msh" }
|
||||
fparkan-nres = { path = "../../crates/fparkan-nres" }
|
||||
fparkan-resource = { path = "../../crates/fparkan-resource" }
|
||||
fparkan-inspection = { path = "../../crates/fparkan-inspection" }
|
||||
fparkan-render = { path = "../../crates/fparkan-render" }
|
||||
fparkan-rsli = { path = "../../crates/fparkan-rsli" }
|
||||
fparkan-terrain-format = { path = "../../crates/fparkan-terrain-format" }
|
||||
fparkan-texm = { path = "../../crates/fparkan-texm" }
|
||||
fparkan-vfs = { path = "../../crates/fparkan-vfs" }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -2,19 +2,16 @@
|
||||
#![allow(clippy::print_stderr, clippy::print_stdout)]
|
||||
//! `FParkan` asset viewer composition root.
|
||||
|
||||
use fparkan_msh::{decode_msh, validate_msh};
|
||||
use fparkan_nres::{decode as decode_nres, ReadProfile as NresReadProfile};
|
||||
use fparkan_inspection::{
|
||||
inspect_land_file, inspect_model_from_root, inspect_texture_from_root, ArchiveInspection, LandFileKind,
|
||||
MapInspection, NresEntrySummary,
|
||||
};
|
||||
use fparkan_render::{
|
||||
build_commands, CameraSnapshot, DrawId, GpuMaterialId, GpuMeshId, IndexRange, RenderPhase,
|
||||
RenderProfile, RenderSnapshot, RenderSnapshotDraw,
|
||||
};
|
||||
use fparkan_resource::{archive_path, resource_name, CachedResourceRepository, ResourceRepository};
|
||||
use fparkan_terrain_format::{decode_land_map, decode_land_msh};
|
||||
use fparkan_texm::decode_texm;
|
||||
use fparkan_vfs::DirectoryVfs;
|
||||
use std::fmt::Write;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
fn main() {
|
||||
let args = std::env::args().skip(1).collect::<Vec<_>>();
|
||||
@@ -44,35 +41,27 @@ fn run(args: &[String]) -> Result<String, String> {
|
||||
fn inspect_archive(args: &[String]) -> Result<String, String> {
|
||||
let file = parse_file(args)?;
|
||||
let limit = parse_limit(args)?;
|
||||
let bytes = std::fs::read(&file).map_err(|err| format!("{}: {err}", file.display()))?;
|
||||
if bytes.starts_with(b"NRes") {
|
||||
let document = decode_nres(
|
||||
Arc::from(bytes.into_boxed_slice()),
|
||||
NresReadProfile::Compatible,
|
||||
)
|
||||
.map_err(|err| err.to_string())?;
|
||||
let sample = render_nres_entries(&document, limit);
|
||||
return Ok(format!(
|
||||
let inspection = fparkan_inspection::inspect_archive_file(&file, limit)?;
|
||||
|
||||
match inspection {
|
||||
ArchiveInspection::Nres {
|
||||
entries,
|
||||
lookup_order_valid,
|
||||
sample,
|
||||
} => Ok(format!(
|
||||
"{{\"kind\":\"NRes\",\"path\":{},\"entries\":{},\"lookup_order_valid\":{},\"sample\":[{}]}}",
|
||||
json_string(&file.display().to_string()),
|
||||
document.entries().len(),
|
||||
document.lookup_order_valid(),
|
||||
sample
|
||||
));
|
||||
}
|
||||
if bytes.get(0..4) == Some(b"NL\0\x01") {
|
||||
let document = fparkan_rsli::decode(
|
||||
Arc::from(bytes.into_boxed_slice()),
|
||||
fparkan_rsli::ReadProfile::Compatible,
|
||||
)
|
||||
.map_err(|err| err.to_string())?;
|
||||
return Ok(format!(
|
||||
entries,
|
||||
lookup_order_valid,
|
||||
render_nres_entries(&sample)
|
||||
)),
|
||||
ArchiveInspection::Rsli { entries } => Ok(format!(
|
||||
"{{\"kind\":\"RsLi\",\"path\":{},\"entries\":{}}}",
|
||||
json_string(&file.display().to_string()),
|
||||
document.entries().len()
|
||||
));
|
||||
entries
|
||||
)),
|
||||
ArchiveInspection::Unsupported => Err(format!("{}: unsupported archive magic", file.display())),
|
||||
}
|
||||
Err(format!("{}: unsupported archive magic", file.display()))
|
||||
}
|
||||
|
||||
fn inspect_model(args: &[String]) -> Result<String, String> {
|
||||
@@ -81,21 +70,18 @@ fn inspect_model(args: &[String]) -> Result<String, String> {
|
||||
}
|
||||
|
||||
let query = parse_resource_query(args)?;
|
||||
let bytes = read_resource(&query)?;
|
||||
let nested = decode_nres(bytes, NresReadProfile::Compatible).map_err(|err| err.to_string())?;
|
||||
let document = decode_msh(&nested).map_err(|err| err.to_string())?;
|
||||
let model = validate_msh(&document).map_err(|err| err.to_string())?;
|
||||
let inspection = inspect_model_from_root(&query.root, &query.archive, &query.name)?;
|
||||
|
||||
Ok(format!(
|
||||
"{{\"kind\":\"model\",\"archive\":{},\"name\":{},\"streams\":{},\"nodes\":{},\"slots\":{},\"positions\":{},\"indices\":{},\"batches\":{}}}",
|
||||
json_string(&query.archive),
|
||||
json_string(&query.name),
|
||||
document.streams().len(),
|
||||
model.node_count,
|
||||
model.slots.len(),
|
||||
model.positions.len(),
|
||||
model.indices.len(),
|
||||
model.batches.len()
|
||||
inspection.streams,
|
||||
inspection.nodes,
|
||||
inspection.slots,
|
||||
inspection.positions,
|
||||
inspection.indices,
|
||||
inspection.batches
|
||||
))
|
||||
}
|
||||
|
||||
@@ -139,54 +125,54 @@ impl ViewerModelService {
|
||||
|
||||
fn inspect_texture(args: &[String]) -> Result<String, String> {
|
||||
let query = parse_resource_query(args)?;
|
||||
let document = decode_texm(read_resource(&query)?).map_err(|err| err.to_string())?;
|
||||
let inspection = inspect_texture_from_root(&query.root, &query.archive, &query.name)?;
|
||||
|
||||
Ok(format!(
|
||||
"{{\"kind\":\"texture\",\"archive\":{},\"name\":{},\"width\":{},\"height\":{},\"format\":{},\"mips\":{},\"pages\":{}}}",
|
||||
json_string(&query.archive),
|
||||
json_string(&query.name),
|
||||
document.width(),
|
||||
document.height(),
|
||||
json_string(&format!("{:?}", document.format())),
|
||||
document.mip_count(),
|
||||
document.page_rects().len()
|
||||
inspection.width,
|
||||
inspection.height,
|
||||
json_string(&inspection.format),
|
||||
inspection.mips,
|
||||
inspection.pages
|
||||
))
|
||||
}
|
||||
|
||||
fn inspect_map(args: &[String]) -> Result<String, String> {
|
||||
let file = parse_file(args)?;
|
||||
let kind = parse_option(args, &["--kind"]).ok_or_else(|| "missing --kind".to_string())?;
|
||||
let bytes = std::fs::read(&file).map_err(|err| format!("{}: {err}", file.display()))?;
|
||||
let nres = decode_nres(
|
||||
Arc::from(bytes.into_boxed_slice()),
|
||||
NresReadProfile::Compatible,
|
||||
)
|
||||
.map_err(|err| err.to_string())?;
|
||||
let inspection = inspect_land_file(
|
||||
&file,
|
||||
match kind.as_str() {
|
||||
"land-msh" => LandFileKind::LandMsh,
|
||||
"land-map" => LandFileKind::LandMap,
|
||||
_ => return Err(format!("unknown map kind: {kind}")),
|
||||
},
|
||||
)?;
|
||||
|
||||
match kind.as_str() {
|
||||
"land-msh" => {
|
||||
let land = decode_land_msh(&nres).map_err(|err| err.to_string())?;
|
||||
Ok(format!(
|
||||
"{{\"kind\":\"land-msh\",\"path\":{},\"streams\":{},\"positions\":{},\"faces\":{},\"slots\":{}}}",
|
||||
json_string(&file.display().to_string()),
|
||||
land.streams.len(),
|
||||
land.positions.len(),
|
||||
land.faces.len(),
|
||||
land.slots.slots_raw.len()
|
||||
))
|
||||
}
|
||||
"land-map" => {
|
||||
let land = decode_land_map(&nres).map_err(|err| err.to_string())?;
|
||||
Ok(format!(
|
||||
"{{\"kind\":\"land-map\",\"path\":{},\"areals\":{},\"declared_areals\":{},\"grid_width\":{},\"grid_height\":{}}}",
|
||||
json_string(&file.display().to_string()),
|
||||
land.areals.len(),
|
||||
land.areal_count,
|
||||
land.grid.cells_x,
|
||||
land.grid.cells_y
|
||||
))
|
||||
}
|
||||
_ => Err(format!("unknown map kind: {kind}")),
|
||||
Ok(render_map_inspection_json(&file.display().to_string(), &kind, &inspection))
|
||||
}
|
||||
|
||||
fn render_map_inspection_json(path: &str, kind: &str, inspection: &MapInspection) -> String {
|
||||
match kind {
|
||||
"land-msh" => format!(
|
||||
"{{\"kind\":\"land-msh\",\"path\":{},\"streams\":{},\"positions\":{},\"faces\":{},\"slots\":{}}}",
|
||||
json_string(path),
|
||||
inspection.streams,
|
||||
inspection.positions,
|
||||
inspection.faces,
|
||||
inspection.slots
|
||||
),
|
||||
"land-map" => format!(
|
||||
"{{\"kind\":\"land-map\",\"path\":{},\"areals\":{},\"declared_areals\":{},\"grid_width\":{},\"grid_height\":{}}}",
|
||||
json_string(path),
|
||||
inspection.areals,
|
||||
inspection.declared_areals,
|
||||
inspection.grid_width,
|
||||
inspection.grid_height
|
||||
),
|
||||
_ => unreachable!("invalid land kind: {kind}"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,19 +191,6 @@ fn parse_resource_query(args: &[String]) -> Result<ResourceQuery, String> {
|
||||
})
|
||||
}
|
||||
|
||||
fn read_resource(query: &ResourceQuery) -> Result<Arc<[u8]>, String> {
|
||||
let repository = CachedResourceRepository::new(Arc::new(DirectoryVfs::new(&query.root)));
|
||||
let archive = repository
|
||||
.open_archive(&archive_path(query.archive.as_bytes()).map_err(|err| err.to_string())?)
|
||||
.map_err(|err| err.to_string())?;
|
||||
let entry = repository
|
||||
.find(archive, &resource_name(query.name.as_bytes()))
|
||||
.map_err(|err| err.to_string())?
|
||||
.ok_or_else(|| format!("resource not found: {}/{}", query.archive, query.name))?;
|
||||
let bytes = repository.read(entry).map_err(|err| err.to_string())?;
|
||||
Ok(Arc::from(bytes.into_owned()))
|
||||
}
|
||||
|
||||
fn parse_file(args: &[String]) -> Result<PathBuf, String> {
|
||||
parse_path_option(args, &["--file"], "--file")
|
||||
}
|
||||
@@ -233,19 +206,19 @@ fn parse_limit(args: &[String]) -> Result<usize, String> {
|
||||
.map(|value| value.unwrap_or(0))
|
||||
}
|
||||
|
||||
fn render_nres_entries(document: &fparkan_nres::NresDocument, limit: usize) -> String {
|
||||
fn render_nres_entries(entries: &[NresEntrySummary]) -> String {
|
||||
let mut out = String::new();
|
||||
for (index, entry) in document.entries().iter().take(limit).enumerate() {
|
||||
for (index, entry) in entries.iter().enumerate() {
|
||||
if index > 0 {
|
||||
out.push(',');
|
||||
}
|
||||
let name = String::from_utf8_lossy(entry.name_bytes());
|
||||
let name = &entry.name;
|
||||
let _ = write!(
|
||||
out,
|
||||
"{{\"name\":{},\"type\":{},\"size\":{}}}",
|
||||
json_string(&name),
|
||||
entry.meta().type_id,
|
||||
entry.meta().data_size
|
||||
json_string(name),
|
||||
entry.type_id,
|
||||
entry.data_size
|
||||
);
|
||||
}
|
||||
out
|
||||
@@ -278,7 +251,7 @@ fn json_string(value: &str) -> String {
|
||||
'\r' => out.push_str("\\r"),
|
||||
'\t' => out.push_str("\\t"),
|
||||
c if c.is_control() => {
|
||||
let _ = write!(out, "\\u{:04x}", u32::from(c));
|
||||
let _ = write!(out, "\\u{:04x}", c as u32);
|
||||
}
|
||||
c => out.push(c),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user