core: Add inital storage backend implementation

Currently SharedObjects are encoded and decoded from JSON via the
StorageBackend, also provided is a basic in-memory implementation
This commit is contained in:
CUB3D 2020-06-12 19:27:20 +01:00
parent 539b4b0f63
commit 48693e4a7a
7 changed files with 230 additions and 19 deletions

7
Cargo.lock generated
View File

@ -1051,6 +1051,11 @@ dependencies = [
"wasm-bindgen 0.2.63 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "json"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "kernel32-sys"
version = "0.2.2"
@ -1866,6 +1871,7 @@ dependencies = [
"gif 0.10.3 (registry+https://github.com/rust-lang/crates.io-index)",
"indexmap 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"jpeg-decoder 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)",
"json 0.12.4 (registry+https://github.com/rust-lang/crates.io-index)",
"libflate 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"minimp3 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2919,6 +2925,7 @@ dependencies = [
"checksum jni-sys 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
"checksum jpeg-decoder 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)" = "5b47b4c4e017b01abdc5bcc126d2d1002e5a75bbe3ce73f9f4f311a916363704"
"checksum js-sys 0.3.40 (registry+https://github.com/rust-lang/crates.io-index)" = "ce10c23ad2ea25ceca0093bd3192229da4c5b3c0f2de499c1ecac0d98d452177"
"checksum json 0.12.4 (registry+https://github.com/rust-lang/crates.io-index)" = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd"
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
"checksum lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f"

View File

@ -29,6 +29,7 @@ url = "2.1.0"
weak-table = "0.2.3"
percent-encoding = "2.1.0"
thiserror = "1.0"
json = "0.12.4"
[dependencies.jpeg-decoder]
version = "0.1.19"

View File

@ -5,6 +5,7 @@ use enumset::EnumSet;
use crate::context::UpdateContext;
use crate::avm1::return_value::ReturnValue;
use crate::avm1::object::Object;
use json::JsonValue;
pub fn delete_all<'gc>(
@ -27,16 +28,91 @@ pub fn get_disk_usage<'gc>(
Ok(Value::Undefined.into())
}
fn parse_json<'gc>(json_obj: JsonValue, avm: &mut Avm1<'gc>, object: Object<'gc>, context: &mut UpdateContext<'_, 'gc, '_>) {
for entry in json_obj.entries() {
match entry.1 {
JsonValue::Null => {
object.define_value(
context.gc_context,
entry.0,
Value::Null,
EnumSet::empty()
);
},
JsonValue::Short(s) => {
let val: String = s.as_str().to_string();
object.define_value(
context.gc_context,
entry.0,
Value::String(val),
EnumSet::empty()
);
},
JsonValue::String(s) => {
object.define_value(
context.gc_context,
entry.0,
Value::String(s.clone()),
EnumSet::empty()
);
},
JsonValue::Number(f) => {
log::warn!("[SharedObject] {} = {}", entry.0, f);
let val: f64 = f.clone().into();
object.define_value(
context.gc_context,
entry.0,
Value::Number(val),
EnumSet::empty()
);
},
JsonValue::Boolean(b) => {
log::warn!("[SharedObject] {} = {}", entry.0, b);
object.define_value(
context.gc_context,
entry.0,
Value::Bool(*b),
EnumSet::empty()
);
},
JsonValue::Object(o) => {
let so = avm.prototypes().object;
let obj = so.new(avm, context, so, &[]).unwrap();
let _ = crate::avm1::globals::object::constructor(avm, context, obj, &[]).unwrap();
parse_json(JsonValue::Object(o.clone()), avm,obj, context);
object.define_value(
context.gc_context,
entry.0,
Value::Object(obj),
EnumSet::empty()
);
},
JsonValue::Array(_) => {},
}
}
}
pub fn get_local<'gc>(
_avm: &mut Avm1<'gc>,
_action_context: &mut UpdateContext<'_, 'gc, '_>,
avm: &mut Avm1<'gc>,
action_context: &mut UpdateContext<'_, 'gc, '_>,
_this: Object<'gc>,
_args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
let so = _avm.prototypes().shared_object;
let obj = so.new(_avm, _action_context, so, &[])?;
let _ = constructor(_avm, _action_context, obj, &[])?;
//TODO: use args and restore existing state (after disk backend)
let so = avm.prototypes().shared_object;
let obj = so.new(avm, action_context, so, &[])?;
let _ = constructor(avm, action_context, obj, &[])?;
let saved = action_context.storage.get_string("tmp".to_string());
if let Some(saved_data) = saved {
let data = obj.get("data", avm, action_context).unwrap().as_object().unwrap();
//TODO: error handle
let js = json::parse(&saved_data).unwrap();
parse_json(js, avm, data, action_context);
}
// log::warn!("SharedObject.getLocal() not implemented");
Ok(obj.into())
@ -120,12 +196,20 @@ pub fn create_shared_object_object<'gc>(
}
pub fn clear<'gc>(
_avm: &mut Avm1<'gc>,
_action_context: &mut UpdateContext<'_, 'gc, '_>,
_this: Object<'gc>,
avm: &mut Avm1<'gc>,
action_context: &mut UpdateContext<'_, 'gc, '_>,
this: Object<'gc>,
_args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
log::warn!("SharedObject.clear() not implemented");
//TODO: does this remove elements not in data prop (e.g. recreate entire obj) or does it just remove children of prop
let data = this.get("data", avm, action_context).unwrap().as_object().unwrap();
for k in &data.get_keys(avm) {
data.delete(avm, action_context.gc_context, k);
}
Ok(Value::Undefined.into())
}
@ -149,31 +233,64 @@ pub fn connect<'gc>(
Ok(Value::Undefined.into())
}
fn recursive_serialize<'gc>(avm: &mut Avm1<'gc>, action_context: &mut UpdateContext<'_, 'gc, '_>, obj: Object<'gc>, json_obj: &mut JsonValue) {
for k in &obj.get_keys(avm) {
let elem = obj.get(k, avm, action_context).unwrap();
match elem {
//TODO: should never happen
Value::Undefined => log::warn!(" [SharedObject] {} = Undef", k),
Value::Null => json_obj[k] = JsonValue::Null,
Value::Bool(b) => json_obj[k] = b.into(),
Value::Number(f) => json_obj[k] = f.into(),
Value::String(s) => json_obj[k] = s.into(),
Value::Object(o) => {
// Don't attempt to serialize functions, etc
if !o.is_instance_of(avm, action_context, o, avm.prototypes().function).unwrap() {
let mut sub_data_json = JsonValue::new_object();
recursive_serialize(avm, action_context, o, &mut sub_data_json);
json_obj[k] = sub_data_json;
}
},
}
}
}
pub fn flush<'gc>(
_avm: &mut Avm1<'gc>,
_action_context: &mut UpdateContext<'_, 'gc, '_>,
_this: Object<'gc>,
_args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
log::warn!("SharedObject.flush() not implemented");
//TODO: consider args
let data = _this.get("data", _avm, _action_context).unwrap().as_object().unwrap();
let mut data_json = JsonValue::new_object();
for key in &data.get_keys(_avm) {
log::warn!("[SharedObject] {} = {:?}", key, &data.get(key, _avm, _action_context));
}
recursive_serialize(_avm, _action_context,data, &mut data_json);
//TODO: somehow need to know the name of where to save it to (hidden property?)
_action_context.storage.put_string("tmp".into(), data_json.dump());
//TODO: return value
Ok(Value::Undefined.into())
}
pub fn get_size<'gc>(
_avm: &mut Avm1<'gc>,
_action_context: &mut UpdateContext<'_, 'gc, '_>,
avm: &mut Avm1<'gc>,
action_context: &mut UpdateContext<'_, 'gc, '_>,
_this: Object<'gc>,
_args: &[Value<'gc>],
args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
log::warn!("SharedObject.getSize() not implemented");
Ok(Value::Undefined.into())
let name = args.get(0)
.unwrap_or(&Value::Undefined)
.to_owned()
.coerce_to_string(avm, action_context)?;
// TODO: what does this return if the item dosent exist
let size = action_context.storage.get_size(name).unwrap_or(0) as f64;
Ok(size.into())
}
pub fn send<'gc>(

View File

@ -2,3 +2,4 @@ pub mod audio;
pub mod input;
pub mod navigator;
pub mod render;
pub mod storage;

View File

@ -0,0 +1,62 @@
use downcast_rs::Downcast;
use std::collections::HashMap;
pub trait StorageBackend: Downcast {
fn get_string(&self, name: String) -> Option<String>;
fn put_string(&mut self, name: String, value: String);
fn get_size(&self, name: String) -> Option<usize> {
self.get_string(name).map(|x| x.as_bytes().len())
}
}
impl_downcast!(StorageBackend);
pub struct MemoryStorageBackend {
pub map: HashMap<String, String>
}
impl Default for MemoryStorageBackend {
fn default() -> Self {
MemoryStorageBackend {
map: HashMap::new()
}
}
}
impl StorageBackend for MemoryStorageBackend {
fn get_string(&self, name: String) -> Option<String> {
self.map.get(&name).map(|s| s.clone())
}
fn put_string(&mut self, name: String, value: String) {
self.map.insert(name, value);
}
}
// impl SharedObjectBackend for MemoryBackend {
// }
//
// // In the desktop player
// struct DiskBackend {}
//
// impl SharedObjectBackend for DiskBackend {
// pub fn get_obj_json(name: String) -> String {
// // read from file
// }
// pub fn set_obj_json(name: String, value: String) {
// // write to file
// //TODO: if slow maybe keep and internal cache, also consider async io for fast flushing
// }
//
// pub fn clear() {
// // Delete the file, modifying the avm objects from any of these will be done in shared_object
// }
// }
//
// // for web player
// struct LocalStorageBackend {}
//
// //TODO: check the issue about this, need to prefix with url of site somehow to avoid collisions

View File

@ -18,6 +18,7 @@ use rand::rngs::SmallRng;
use std::collections::BTreeMap;
use std::collections::VecDeque;
use std::sync::{Arc, Mutex, Weak};
use crate::backend::storage::StorageBackend;
/// `UpdateContext` holds shared data that is used by the various subsystems of Ruffle.
/// `Player` crates this when it begins a tick and passes it through the call stack to
@ -64,6 +65,9 @@ pub struct UpdateContext<'a, 'gc, 'gc_context> {
/// The input backend, used to detect user interactions.
pub input: &'a mut dyn InputBackend,
/// The storage backend, used for storing persistent state
pub storage: &'a mut dyn StorageBackend,
/// The RNG, used by the AVM `RandomNumber` opcode, `Math.random(),` and `random()`.
pub rng: &'a mut SmallRng,

View File

@ -22,6 +22,7 @@ use std::collections::BTreeMap;
use std::convert::TryFrom;
use std::ops::DerefMut;
use std::sync::{Arc, Mutex, Weak};
use crate::backend::storage::{StorageBackend, MemoryStorageBackend};
static DEVICE_FONT_TAG: &[u8] = include_bytes!("../assets/noto-sans-definefont3.bin");
@ -89,6 +90,7 @@ type Audio = Box<dyn AudioBackend>;
type Navigator = Box<dyn NavigatorBackend>;
type Renderer = Box<dyn RenderBackend>;
type Input = Box<dyn InputBackend>;
type Storage = Box<dyn StorageBackend>;
pub struct Player {
/// The version of the player we're emulating.
@ -116,6 +118,8 @@ pub struct Player {
view_matrix: Matrix,
inverse_view_matrix: Matrix,
storage: Storage,
rng: SmallRng,
gc_arena: GcArena,
@ -157,6 +161,7 @@ impl Player {
navigator: Navigator,
input: Input,
movie: SwfMovie,
storage: Storage
) -> Result<Arc<Mutex<Self>>, Error> {
let movie = Arc::new(movie);
@ -188,6 +193,7 @@ impl Player {
view_matrix: Default::default(),
inverse_view_matrix: Default::default(),
rng: SmallRng::from_seed([0u8; 16]), // TODO(Herschel): Get a proper seed on all platforms.
gc_arena: GcArena::new(ArenaParameters::default(), |gc_context| {
@ -242,6 +248,7 @@ impl Player {
self_reference: None,
system: SystemProperties::default(),
instance_counter: 0,
storage
};
player.mutate_with_update_context(|avm, context| {
@ -849,7 +856,11 @@ impl Player {
stage_height,
player,
system_properties,
<<<<<<< HEAD
instance_counter,
=======
storage,
>>>>>>> 0ca1eb0... core: Add inital storage backend implementation
) = (
self.player_version,
self.global_time,
@ -865,7 +876,11 @@ impl Player {
Twips::from_pixels(self.movie_height.into()),
self.self_reference.clone(),
&mut self.system,
<<<<<<< HEAD
&mut self.instance_counter,
=======
self.storage.deref_mut(),
>>>>>>> 0ca1eb0... core: Add inital storage backend implementation
);
self.gc_arena.mutate(|gc_context, gc_root| {
@ -896,7 +911,11 @@ impl Player {
player,
load_manager,
system: system_properties,
<<<<<<< HEAD
instance_counter,
=======
storage
>>>>>>> 0ca1eb0... core: Add inital storage backend implementation
};
let ret = f(avm, &mut update_context);