Skip to content

Commit

Permalink
port: Kludge together WhenceUniverse::save() support, for native JS…
Browse files Browse the repository at this point in the history
…ON only.
  • Loading branch information
kpreid committed Oct 20, 2023
1 parent a3d21cd commit 30f3895
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 24 deletions.
31 changes: 27 additions & 4 deletions all-is-cubes-port/src/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ pub trait Fileish: fmt::Debug + Send + Sync {
///
/// TODO: This should probably be async.
fn read(&self) -> Result<Vec<u8>, io::Error>;

/// Overwrites the file contents, if possible.
///
/// TODO: This should probably be async.
///
/// TODO: Need a way to communicate “this is definitely never writability”.
fn write(&self, data: &[u8]) -> Result<(), io::Error>;
}

// TODO: when Rust has generic associated types we will no longer
Expand All @@ -40,17 +47,23 @@ impl Fileish for PathBuf {
fn read(&self) -> Result<Vec<u8>, io::Error> {
std::fs::read(self)
}

fn write(&self, data: &[u8]) -> Result<(), io::Error> {
std::fs::write(self, data)
}
}

/// General-purpose implementation of [`Fileish`].
///
/// TODO: figure out how writing works for this
pub struct NonDiskFile<O> {
name: String,
opener: O,
reader: O,
}

impl<O> fmt::Debug for NonDiskFile<O> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self { name, opener: _ } = self;
let Self { name, reader: _ } = self;
f.debug_struct("NonDiskFile")
.field("name", name)
.finish_non_exhaustive()
Expand All @@ -60,7 +73,10 @@ impl<O> fmt::Debug for NonDiskFile<O> {
impl<O> NonDiskFile<O> {
/// Construct a new [`NonDiskFile`] from its parts.
pub fn from_name_and_data_source(name: String, opener: O) -> Self {
Self { name, opener }
Self {
name,
reader: opener,
}
}
}

Expand All @@ -77,6 +93,13 @@ where
}

fn read(&self) -> Result<Vec<u8>, io::Error> {
(self.opener)()
(self.reader)()
}

fn write(&self, _data: &[u8]) -> Result<(), io::Error> {
Err(io::Error::new(
io::ErrorKind::Unsupported,
"writing is not yet implemented for NonDiskFile",
))
}
}
38 changes: 28 additions & 10 deletions all-is-cubes-port/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@
#![warn(missing_docs)]

use std::ffi::OsString;
use std::fs;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::{fs, io};

use futures_core::future::BoxFuture;

Expand Down Expand Up @@ -123,7 +123,10 @@ pub async fn export_to_path(
destination: PathBuf,
) -> Result<(), crate::ExportError> {
match format {
ExportFormat::AicJson => native::export_native_json(progress, source, destination).await,
ExportFormat::AicJson => {
let mut writer = io::BufWriter::new(fs::File::create(destination)?);
native::export_native_json(progress, source, &mut writer).await
}
ExportFormat::DotVox => {
// TODO: async file IO?
mv::export_dot_vox(progress, source, fs::File::create(destination)?).await
Expand Down Expand Up @@ -216,10 +219,11 @@ impl all_is_cubes::save::WhenceUniverse for PortWhence {
}

fn can_save(&self) -> bool {
// TODO: implement this along with save()
#[allow(unused)]
let _ = self.save_format;
false
match self.save_format {
Some(ExportFormat::AicJson) => true,
Some(_) => false,
None => false,
}
}

fn load(
Expand All @@ -235,10 +239,24 @@ impl all_is_cubes::save::WhenceUniverse for PortWhence {
universe: &Universe,
progress: YieldProgress,
) -> BoxFuture<'static, Result<(), Box<dyn std::error::Error + Send + Sync>>> {
// TODO: in order to implement this we need to be able to write to a `Fileish`
// or have an accompanying destination
let _ = (universe, progress, self.save_format);
Box::pin(async { Err("saving via `WhenceUniverse` is not yet implemented".into()) })
let source = ExportSet::all_of_universe(universe);
let save_format = self.save_format;
let file = self.file.clone();
Box::pin(async move {
// TODO: merge this and `export_to_path()`
match save_format {
Some(ExportFormat::AicJson) => {
let mut buf = Vec::new();
native::export_native_json(progress, source, &mut buf).await?;
file.write(&buf)?;
Ok(())
}
Some(_) => {
Err("saving this format via `WhenceUniverse` is not yet implemented".into())
}
None => Err("saving the file format that was loaded is not supported".into()),
}
})
}
}

Expand Down
12 changes: 4 additions & 8 deletions all-is-cubes-port/src/native.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use std::path::PathBuf;
use std::{fs, io};
use std::io;

use all_is_cubes::universe::Universe;
use all_is_cubes::util::YieldProgress;
Expand Down Expand Up @@ -29,18 +28,15 @@ pub(crate) fn import_native_json(
})
}

/// The `destination` should be buffered for efficiency.
pub(crate) async fn export_native_json(
progress: YieldProgress,
source: ExportSet,
destination: PathBuf,
destination: &mut (dyn io::Write + Send),
) -> Result<(), ExportError> {
// TODO: Spin off a blocking thread to perform this export
let ExportSet { contents } = source;
serde_json::to_writer(
io::BufWriter::new(fs::File::create(destination)?),
&contents,
)
.map_err(|error| {
serde_json::to_writer(destination, &contents).map_err(|error| {
// TODO: report non-IO errors distinctly
ExportError::Write(io::Error::new(io::ErrorKind::Other, error))
})?;
Expand Down
56 changes: 54 additions & 2 deletions all-is-cubes-port/src/tests.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
use std::error::Error as _;
use std::fs;
use std::sync::Arc;

use all_is_cubes::block;
use pretty_assertions::assert_eq;
use serde_json::json;

use all_is_cubes::block::{self, AIR};
use all_is_cubes::util::{assert_send_sync, yield_progress_for_testing};

use crate::file::NonDiskFile;
use crate::{
load_universe_from_file, BlockDef, ExportError, ExportSet, ImportError, Path, PathBuf, Universe,
export_to_path, load_universe_from_file, BlockDef, ExportError, ExportFormat, ExportSet,
ImportError, Path, PathBuf, Universe,
};

#[test]
Expand Down Expand Up @@ -54,3 +59,50 @@ fn member_export_path() {
PathBuf::from("/export/data.ext"),
);
}

#[tokio::test]
async fn port_whence_load_then_save() {
let tmp_dir = tempfile::tempdir().unwrap();
let path: PathBuf = tmp_dir.path().join("foo.alliscubesjson");

// Write initial state.
export_to_path(
yield_progress_for_testing(),
ExportFormat::AicJson,
ExportSet::all_of_universe(&Universe::new()),
path.clone(),
)
.await
.unwrap();

// Load it again, producing a universe containing `PortWhence`.
let mut universe =
load_universe_from_file(yield_progress_for_testing(), Arc::new(path.clone()))
.await
.unwrap();

// Make a change.
universe.insert("hello".into(), BlockDef::new(AIR)).unwrap();

// Save it.
universe
.whence
.save(&universe, yield_progress_for_testing())
.await
.unwrap();

// Check the saved result
assert_eq!(
serde_json::from_reader::<_, serde_json::Value>(fs::File::open(path).unwrap()).unwrap(),
json!({
"type": "UniverseV1",
"members": [
{
"name": {"Specific": "hello"},
"member_type": "Block",
"value": {"type": "BlockV1", "primitive": {"type": "AirV1"}},
}
]
})
);
}

0 comments on commit 30f3895

Please sign in to comment.