[add] DataStorage

This commit is contained in:
2020-03-10 22:59:59 +03:00
parent 46b971bfd4
commit d375f07bed
9 changed files with 215 additions and 89 deletions

View 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);
}
}

View File

@@ -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>;
}

View File

@@ -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>;

View File

@@ -129,6 +129,7 @@ class GameUtil {
}
}
return {
id: preset.imageId,
status: READY,
preset: preset,
parts: parts,

View File

@@ -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))
);
}
}

View File

@@ -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 _:

View File

@@ -78,12 +78,13 @@ import ru.m.puzzlez.view.PuzzleImageView;
}
private function start(imageId:ImageId):Void {
var state = gameStorage.load(imageId);
if (state != null) {
switcher.change(GameFrame.ID, state);
} else {
switcher.change(PresetFrame.ID, imageId);
}
gameStorage.get(imageId).then(state -> {
if (state != null) {
switcher.change(GameFrame.ID, state);
} else {
switcher.change(PresetFrame.ID, imageId);
}
});
}
private function back():Void {

View File

@@ -64,14 +64,15 @@ enum Action {
public static function factory(index:Int, imageId:ImageId):PuzzleImageView {
var result = new PuzzleImageView();
result.imageId = imageId;
var state = gameStorage.load(imageId);
if (state != null) {
var progress = GameUtil.calcProgress(state);
result.text = '${progress.complete}/${progress.total}';
result.cleanButton.visible = true;
} else if (imageId.source == FileSource.ID) {
result.removeButton.visible = true;
}
gameStorage.get(imageId).then(state -> {
if (state != null) {
var progress = GameUtil.calcProgress(state);
result.text = '${progress.complete}/${progress.total}';
result.cleanButton.visible = true;
} else if (imageId.source == FileSource.ID) {
result.removeButton.visible = true;
}
});
return result;
}
}

View File

@@ -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();
}