[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;
|
import promhx.Promise;
|
||||||
|
|
||||||
|
typedef Filter = Map<String, String>;
|
||||||
|
|
||||||
typedef Page = {
|
typedef Page = {
|
||||||
var index:Int;
|
var index:Int;
|
||||||
var count:Int;
|
var count:Int;
|
||||||
@:optional var order:String;
|
@:optional var order:Array<String>;
|
||||||
@:optional var filter:String;
|
@:optional var filter:Filter;
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef DataPage<T> = {
|
typedef DataPage<T> = {
|
||||||
@@ -15,6 +17,10 @@ typedef DataPage<T> = {
|
|||||||
var data:Array<T>;
|
var data:Array<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IDataSource<T> {
|
interface IDataSource<I, D:{id:I}> {
|
||||||
public function getPage(page:Page):Promise<DataPage<T>>;
|
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;
|
package ru.m.puzzlez.core;
|
||||||
|
|
||||||
|
import ru.m.puzzlez.core.Id;
|
||||||
|
|
||||||
enum abstract GameStatus(String) from String to String {
|
enum abstract GameStatus(String) from String to String {
|
||||||
var READY = "ready";
|
var READY = "ready";
|
||||||
var STARTED = "started";
|
var STARTED = "started";
|
||||||
@@ -7,6 +9,7 @@ enum abstract GameStatus(String) from String to String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
typedef GameState = {
|
typedef GameState = {
|
||||||
|
var id:ImageId;
|
||||||
var status:GameStatus;
|
var status:GameStatus;
|
||||||
var preset:GamePreset;
|
var preset:GamePreset;
|
||||||
var parts:Array<Part>;
|
var parts:Array<Part>;
|
||||||
|
|||||||
@@ -129,6 +129,7 @@ class GameUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
id: preset.imageId,
|
||||||
status: READY,
|
status: READY,
|
||||||
preset: preset,
|
preset: preset,
|
||||||
parts: parts,
|
parts: parts,
|
||||||
|
|||||||
@@ -1,69 +1,18 @@
|
|||||||
package ru.m.puzzlez.storage;
|
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.GameState;
|
||||||
import ru.m.puzzlez.core.Id;
|
import ru.m.puzzlez.core.Id;
|
||||||
|
import ru.m.data.DataStorage;
|
||||||
|
|
||||||
@:provide class GameStorage implements IDataSource<ImageId> {
|
@:provide class GameStorage extends DataStorage<ImageId, GameState> {
|
||||||
|
inline private static var NAME = "game";
|
||||||
private static var path = "game_1";
|
inline private static var VERSION = 3;
|
||||||
private var statusData:SharedObject;
|
|
||||||
|
|
||||||
public function new() {
|
public function new() {
|
||||||
statusData = SharedObject.getLocal('${path}/status');
|
super(
|
||||||
}
|
'${NAME}/${VERSION}',
|
||||||
|
new DataFilter<GameState>(item -> ["status" => item.status]),
|
||||||
public function getPage(page:Page):Promise<DataPage<ImageId>> {
|
new Converter<ImageId, String>(id -> id.toString(), data -> ImageId.fromString(data))
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,12 +29,12 @@ import ru.m.puzzlez.view.PuzzleImageView;
|
|||||||
|
|
||||||
override public function onShow(data:GameStatus):Void {
|
override public function onShow(data:GameStatus):Void {
|
||||||
status = data;
|
status = data;
|
||||||
page.filter = status;
|
page.filter = ["status" => status];
|
||||||
storage.getPage(page).then(page -> images.data = page.data);
|
storage.getIndexPage(page).then(page -> images.data = page.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function start(id:ImageId):Void {
|
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 {
|
private function onAction(imageId:ImageId, action:Action):Void {
|
||||||
@@ -43,7 +43,7 @@ import ru.m.puzzlez.view.PuzzleImageView;
|
|||||||
ConfirmView.confirm("Delete state?").then(result -> {
|
ConfirmView.confirm("Delete state?").then(result -> {
|
||||||
if (result) {
|
if (result) {
|
||||||
storage.delete(imageId);
|
storage.delete(imageId);
|
||||||
storage.getPage(page).then(page -> images.data = page.data);
|
storage.getIndexPage(page).then(page -> images.data = page.data);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
case _:
|
case _:
|
||||||
|
|||||||
@@ -78,12 +78,13 @@ import ru.m.puzzlez.view.PuzzleImageView;
|
|||||||
}
|
}
|
||||||
|
|
||||||
private function start(imageId:ImageId):Void {
|
private function start(imageId:ImageId):Void {
|
||||||
var state = gameStorage.load(imageId);
|
gameStorage.get(imageId).then(state -> {
|
||||||
if (state != null) {
|
if (state != null) {
|
||||||
switcher.change(GameFrame.ID, state);
|
switcher.change(GameFrame.ID, state);
|
||||||
} else {
|
} else {
|
||||||
switcher.change(PresetFrame.ID, imageId);
|
switcher.change(PresetFrame.ID, imageId);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private function back():Void {
|
private function back():Void {
|
||||||
|
|||||||
@@ -64,14 +64,15 @@ enum Action {
|
|||||||
public static function factory(index:Int, imageId:ImageId):PuzzleImageView {
|
public static function factory(index:Int, imageId:ImageId):PuzzleImageView {
|
||||||
var result = new PuzzleImageView();
|
var result = new PuzzleImageView();
|
||||||
result.imageId = imageId;
|
result.imageId = imageId;
|
||||||
var state = gameStorage.load(imageId);
|
gameStorage.get(imageId).then(state -> {
|
||||||
if (state != null) {
|
if (state != null) {
|
||||||
var progress = GameUtil.calcProgress(state);
|
var progress = GameUtil.calcProgress(state);
|
||||||
result.text = '${progress.complete}/${progress.total}';
|
result.text = '${progress.complete}/${progress.total}';
|
||||||
result.cleanButton.visible = true;
|
result.cleanButton.visible = true;
|
||||||
} else if (imageId.source == FileSource.ID) {
|
} else if (imageId.source == FileSource.ID) {
|
||||||
result.removeButton.visible = true;
|
result.removeButton.visible = true;
|
||||||
}
|
}
|
||||||
|
});
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,12 +38,16 @@ import ru.m.puzzlez.storage.ImageStorage;
|
|||||||
}
|
}
|
||||||
|
|
||||||
private function refresh():Void {
|
private function refresh():Void {
|
||||||
var startedCount = gameStorage.list(STARTED).length;
|
gameStorage.getIndexPage({index: 0, count: 0, filter: ["status" => STARTED]}).then(page -> {
|
||||||
var completeCount = gameStorage.list(COMPLETE).length;
|
var count = page.total;
|
||||||
loadButton.text = 'Resume (${startedCount})';
|
loadButton.text = 'Resume (${count})';
|
||||||
completeButton.text = 'Complete (${completeCount})';
|
loadButton.disabled = count == 0;
|
||||||
loadButton.disabled = startedCount == 0;
|
});
|
||||||
completeButton.disabled = completeCount == 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 {
|
override public function onShow(data:Dynamic):Void {
|
||||||
@@ -69,7 +73,7 @@ import ru.m.puzzlez.storage.ImageStorage;
|
|||||||
private function clean():Void {
|
private function clean():Void {
|
||||||
ConfirmView.confirm("Really clean all saved data?").then(result -> {
|
ConfirmView.confirm("Really clean all saved data?").then(result -> {
|
||||||
if (result) {
|
if (result) {
|
||||||
gameStorage.clear();
|
//gameStorage.clear();
|
||||||
var fileSource:FileSource = cast storage.sources.get(FileSource.ID);
|
var fileSource:FileSource = cast storage.sources.get(FileSource.ID);
|
||||||
fileSource.clean();
|
fileSource.clean();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user