[add] DataStorage
This commit is contained in:
161
src/haxe/ru/m/data/DataStorage.hx
Normal file
161
src/haxe/ru/m/data/DataStorage.hx
Normal file
@@ -0,0 +1,161 @@
|
||||
package ru.m.data;
|
||||
|
||||
import flash.net.SharedObject;
|
||||
import haxe.DynamicAccess;
|
||||
import haxe.Serializer;
|
||||
import haxe.Unserializer;
|
||||
import promhx.Promise;
|
||||
import ru.m.data.IDataSource;
|
||||
|
||||
class Converter<D, S> {
|
||||
private var serializer:D -> S;
|
||||
private var desirealizer:S -> D;
|
||||
|
||||
public function new(serializer:D -> S, desirealizer:S -> D) {
|
||||
this.serializer = serializer;
|
||||
this.desirealizer = desirealizer;
|
||||
}
|
||||
|
||||
public inline function serialize(item:D):S {
|
||||
return serializer(item);
|
||||
}
|
||||
|
||||
public inline function deserialize(data:S):D {
|
||||
return desirealizer(data);
|
||||
}
|
||||
}
|
||||
|
||||
class DefaultConverter<D> extends Converter<D, String> {
|
||||
public function new () {
|
||||
super(
|
||||
item -> Serializer.run(item),
|
||||
data -> new Unserializer(data).unserialize()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DataFilter<D> extends DefaultConverter<Filter> {
|
||||
private var builder:D -> Filter;
|
||||
|
||||
public function new(builder:D -> Filter) {
|
||||
super();
|
||||
this.builder = builder;
|
||||
}
|
||||
|
||||
public function build(item:D):Filter {
|
||||
return builder(item);
|
||||
}
|
||||
}
|
||||
|
||||
class DataStorage<I, D:{id:I}> implements IDataSource<I, D> {
|
||||
|
||||
inline private static var DATA_KEY = "item";
|
||||
|
||||
private var name:String;
|
||||
private var filterConverter:DataFilter<D>;
|
||||
private var idConverter:Converter<I, String>;
|
||||
private var dataConverter:Converter<D, Dynamic>;
|
||||
private var indexData:SharedObject;
|
||||
|
||||
public function new(
|
||||
name:String,
|
||||
filterConverter:DataFilter<D>,
|
||||
idConverter:Converter<I, String> = null,
|
||||
dataConverter:Converter<D, Dynamic> = null
|
||||
) {
|
||||
this.name = name;
|
||||
this.filterConverter = filterConverter;
|
||||
this.idConverter = idConverter != null ? idConverter : new DefaultConverter();
|
||||
this.dataConverter = dataConverter != null ? dataConverter : new DefaultConverter();
|
||||
this.indexData = SharedObject.getLocal('${name}/index');
|
||||
}
|
||||
|
||||
private function serializeId(id:I):String {
|
||||
return idConverter.serialize(id);
|
||||
}
|
||||
|
||||
private function desirealizeId(data:String):I {
|
||||
return idConverter.deserialize(data);
|
||||
}
|
||||
|
||||
private function buildFilter(item:D):Filter {
|
||||
return filterConverter.build(item);
|
||||
}
|
||||
|
||||
private function checkFilter(filter:Null<Filter>, data:Filter):Bool {
|
||||
if (filter != null) {
|
||||
for (k => v in filter) {
|
||||
if (data.get(k) != v) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private function serialize(item:D):Dynamic {
|
||||
return dataConverter.serialize(item);
|
||||
}
|
||||
|
||||
private function unserialize(data:Dynamic):D {
|
||||
return dataConverter.deserialize(data);
|
||||
}
|
||||
|
||||
public function getIndexPage(page:Page):Promise<DataPage<I>> {
|
||||
var data:DynamicAccess<String> = indexData.data;
|
||||
var result:Array<I> = [];
|
||||
for (k => v in data) {
|
||||
var filter = this.filterConverter.deserialize(v);
|
||||
if (checkFilter(page.filter, filter)) {
|
||||
result.push(desirealizeId(k));
|
||||
}
|
||||
}
|
||||
return Promise.promise({
|
||||
page: page,
|
||||
total: result.length,
|
||||
data: result,
|
||||
});
|
||||
}
|
||||
|
||||
public function getPage(page:Page):Promise<DataPage<D>> {
|
||||
return getIndexPage(page).pipe((indexPage:DataPage<I>) -> {
|
||||
Promise.whenAll([for (id in indexPage.data) get(id)]).then((data:Array<D>) -> {
|
||||
return {
|
||||
page: indexPage.page,
|
||||
total: indexPage.total,
|
||||
data: data,
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public function get(id:I):Promise<D> {
|
||||
var stringId = serializeId(id);
|
||||
var itemData = SharedObject.getLocal('${name}/${stringId}');
|
||||
var result:D = null;
|
||||
if (Reflect.hasField(itemData.data, DATA_KEY)) {
|
||||
result = unserialize(Reflect.field(itemData.data, DATA_KEY));
|
||||
}
|
||||
return Promise.promise(result);
|
||||
}
|
||||
|
||||
public function save(item:D):Promise<D> {
|
||||
var stringId = serializeId(item.id);
|
||||
var itemData = SharedObject.getLocal('${name}/${stringId}');
|
||||
itemData.setProperty(DATA_KEY, serialize(item));
|
||||
itemData.flush();
|
||||
indexData.setProperty(stringId, filterConverter.serialize(filterConverter.build(item)));
|
||||
indexData.flush();
|
||||
return Promise.promise(item);
|
||||
}
|
||||
|
||||
public function delete(id:I):Promise<Bool> {
|
||||
var stringId = serializeId(id);
|
||||
var itemData = SharedObject.getLocal('${name}/${stringId}');
|
||||
itemData.clear();
|
||||
var data:DynamicAccess<String> = indexData.data;
|
||||
data.remove(stringId);
|
||||
indexData.flush();
|
||||
return Promise.promise(true);
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,13 @@ package ru.m.data;
|
||||
|
||||
import promhx.Promise;
|
||||
|
||||
typedef Filter = Map<String, String>;
|
||||
|
||||
typedef Page = {
|
||||
var index:Int;
|
||||
var count:Int;
|
||||
@:optional var order:String;
|
||||
@:optional var filter:String;
|
||||
@:optional var order:Array<String>;
|
||||
@:optional var filter:Filter;
|
||||
}
|
||||
|
||||
typedef DataPage<T> = {
|
||||
@@ -15,6 +17,10 @@ typedef DataPage<T> = {
|
||||
var data:Array<T>;
|
||||
}
|
||||
|
||||
interface IDataSource<T> {
|
||||
public function getPage(page:Page):Promise<DataPage<T>>;
|
||||
interface IDataSource<I, D:{id:I}> {
|
||||
public function getIndexPage(page:Page):Promise<DataPage<I>>;
|
||||
public function getPage(page:Page):Promise<DataPage<D>>;
|
||||
public function get(id:I):Promise<D>;
|
||||
public function save(item:D):Promise<D>;
|
||||
public function delete(id:I):Promise<Bool>;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package ru.m.puzzlez.core;
|
||||
|
||||
import ru.m.puzzlez.core.Id;
|
||||
|
||||
enum abstract GameStatus(String) from String to String {
|
||||
var READY = "ready";
|
||||
var STARTED = "started";
|
||||
@@ -7,6 +9,7 @@ enum abstract GameStatus(String) from String to String {
|
||||
}
|
||||
|
||||
typedef GameState = {
|
||||
var id:ImageId;
|
||||
var status:GameStatus;
|
||||
var preset:GamePreset;
|
||||
var parts:Array<Part>;
|
||||
|
||||
@@ -129,6 +129,7 @@ class GameUtil {
|
||||
}
|
||||
}
|
||||
return {
|
||||
id: preset.imageId,
|
||||
status: READY,
|
||||
preset: preset,
|
||||
parts: parts,
|
||||
|
||||
@@ -1,69 +1,18 @@
|
||||
package ru.m.puzzlez.storage;
|
||||
|
||||
import promhx.Promise;
|
||||
import flash.net.SharedObject;
|
||||
import haxe.DynamicAccess;
|
||||
import haxe.Serializer;
|
||||
import haxe.Unserializer;
|
||||
import ru.m.data.IDataSource;
|
||||
import ru.m.puzzlez.core.GameState;
|
||||
import ru.m.puzzlez.core.Id;
|
||||
import ru.m.data.DataStorage;
|
||||
|
||||
@:provide class GameStorage implements IDataSource<ImageId> {
|
||||
|
||||
private static var path = "game_1";
|
||||
private var statusData:SharedObject;
|
||||
@:provide class GameStorage extends DataStorage<ImageId, GameState> {
|
||||
inline private static var NAME = "game";
|
||||
inline private static var VERSION = 3;
|
||||
|
||||
public function new() {
|
||||
statusData = SharedObject.getLocal('${path}/status');
|
||||
}
|
||||
|
||||
public function getPage(page:Page):Promise<DataPage<ImageId>> {
|
||||
var allData = list(page.filter);
|
||||
allData.sort((a, b) -> Reflect.compare(a.toString().toLowerCase(), b.toString().toLowerCase()));
|
||||
return Promise.promise({
|
||||
page: page,
|
||||
total: allData.length,
|
||||
data: allData.slice(page.index * page.count, page.count),
|
||||
});
|
||||
}
|
||||
|
||||
public function save(state:GameState):Void {
|
||||
statusData.setProperty(state.preset.imageId, Std.string(state.status));
|
||||
statusData.flush();
|
||||
var gameData = SharedObject.getLocal('${path}/${state.preset.imageId}');
|
||||
gameData.setProperty("game", Serializer.run(state));
|
||||
gameData.flush();
|
||||
}
|
||||
|
||||
public function load(imageId:ImageId):Null<GameState> {
|
||||
var gameData = SharedObject.getLocal('${path}/${imageId}');
|
||||
if (Reflect.hasField(gameData.data, "game")) {
|
||||
return new Unserializer(Reflect.field(gameData.data, "game")).unserialize();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function list(status:GameStatus = null):Array<ImageId> {
|
||||
var data:DynamicAccess<GameStatus> = statusData.data;
|
||||
var result:Array<ImageId> = [];
|
||||
for (k => s in data) {
|
||||
if (status == null || status == s) {
|
||||
result.push(k);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public function delete(imageId:ImageId):Void {
|
||||
var gameData = SharedObject.getLocal('${path}/${imageId}');
|
||||
gameData.clear();
|
||||
var data:DynamicAccess<GameStatus> = statusData.data;
|
||||
data.remove(imageId);
|
||||
statusData.flush();
|
||||
}
|
||||
|
||||
public function clear():Void {
|
||||
//ToDo: implement me
|
||||
super(
|
||||
'${NAME}/${VERSION}',
|
||||
new DataFilter<GameState>(item -> ["status" => item.status]),
|
||||
new Converter<ImageId, String>(id -> id.toString(), data -> ImageId.fromString(data))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,12 +29,12 @@ import ru.m.puzzlez.view.PuzzleImageView;
|
||||
|
||||
override public function onShow(data:GameStatus):Void {
|
||||
status = data;
|
||||
page.filter = status;
|
||||
storage.getPage(page).then(page -> images.data = page.data);
|
||||
page.filter = ["status" => status];
|
||||
storage.getIndexPage(page).then(page -> images.data = page.data);
|
||||
}
|
||||
|
||||
private function start(id:ImageId):Void {
|
||||
switcher.change(GameFrame.ID, storage.load(id));
|
||||
storage.get(id).then(state -> switcher.change(GameFrame.ID, state));
|
||||
}
|
||||
|
||||
private function onAction(imageId:ImageId, action:Action):Void {
|
||||
@@ -43,7 +43,7 @@ import ru.m.puzzlez.view.PuzzleImageView;
|
||||
ConfirmView.confirm("Delete state?").then(result -> {
|
||||
if (result) {
|
||||
storage.delete(imageId);
|
||||
storage.getPage(page).then(page -> images.data = page.data);
|
||||
storage.getIndexPage(page).then(page -> images.data = page.data);
|
||||
}
|
||||
});
|
||||
case _:
|
||||
|
||||
@@ -78,12 +78,13 @@ import ru.m.puzzlez.view.PuzzleImageView;
|
||||
}
|
||||
|
||||
private function start(imageId:ImageId):Void {
|
||||
var state = gameStorage.load(imageId);
|
||||
gameStorage.get(imageId).then(state -> {
|
||||
if (state != null) {
|
||||
switcher.change(GameFrame.ID, state);
|
||||
} else {
|
||||
switcher.change(PresetFrame.ID, imageId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private function back():Void {
|
||||
|
||||
@@ -64,7 +64,7 @@ enum Action {
|
||||
public static function factory(index:Int, imageId:ImageId):PuzzleImageView {
|
||||
var result = new PuzzleImageView();
|
||||
result.imageId = imageId;
|
||||
var state = gameStorage.load(imageId);
|
||||
gameStorage.get(imageId).then(state -> {
|
||||
if (state != null) {
|
||||
var progress = GameUtil.calcProgress(state);
|
||||
result.text = '${progress.complete}/${progress.total}';
|
||||
@@ -72,6 +72,7 @@ enum Action {
|
||||
} else if (imageId.source == FileSource.ID) {
|
||||
result.removeButton.visible = true;
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,12 +38,16 @@ import ru.m.puzzlez.storage.ImageStorage;
|
||||
}
|
||||
|
||||
private function refresh():Void {
|
||||
var startedCount = gameStorage.list(STARTED).length;
|
||||
var completeCount = gameStorage.list(COMPLETE).length;
|
||||
loadButton.text = 'Resume (${startedCount})';
|
||||
completeButton.text = 'Complete (${completeCount})';
|
||||
loadButton.disabled = startedCount == 0;
|
||||
completeButton.disabled = completeCount == 0;
|
||||
gameStorage.getIndexPage({index: 0, count: 0, filter: ["status" => STARTED]}).then(page -> {
|
||||
var count = page.total;
|
||||
loadButton.text = 'Resume (${count})';
|
||||
loadButton.disabled = count == 0;
|
||||
});
|
||||
gameStorage.getIndexPage({index: 0, count: 0, filter: ["status" => COMPLETE]}).then(page -> {
|
||||
var total = page.total;
|
||||
completeButton.text = 'Complete (${total})';
|
||||
completeButton.disabled = total == 0;
|
||||
});
|
||||
}
|
||||
|
||||
override public function onShow(data:Dynamic):Void {
|
||||
@@ -69,7 +73,7 @@ import ru.m.puzzlez.storage.ImageStorage;
|
||||
private function clean():Void {
|
||||
ConfirmView.confirm("Really clean all saved data?").then(result -> {
|
||||
if (result) {
|
||||
gameStorage.clear();
|
||||
//gameStorage.clear();
|
||||
var fileSource:FileSource = cast storage.sources.get(FileSource.ID);
|
||||
fileSource.clean();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user