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:
parent
539b4b0f63
commit
48693e4a7a
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>(
|
||||
|
|
|
@ -2,3 +2,4 @@ pub mod audio;
|
|||
pub mod input;
|
||||
pub mod navigator;
|
||||
pub mod render;
|
||||
pub mod storage;
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
@ -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,
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue