diff --git a/src/haxe/ru/m/data/DataStorage.hx b/src/haxe/ru/m/data/DataStorage.hx new file mode 100644 index 0000000..84e3aae --- /dev/null +++ b/src/haxe/ru/m/data/DataStorage.hx @@ -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 { + 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 extends Converter { + public function new () { + super( + item -> Serializer.run(item), + data -> new Unserializer(data).unserialize() + ); + } +} + +class DataFilter extends DefaultConverter { + 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 implements IDataSource { + + inline private static var DATA_KEY = "item"; + + private var name:String; + private var filterConverter:DataFilter; + private var idConverter:Converter; + private var dataConverter:Converter; + private var indexData:SharedObject; + + public function new( + name:String, + filterConverter:DataFilter, + idConverter:Converter = null, + dataConverter:Converter = 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, 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> { + var data:DynamicAccess = indexData.data; + var result:Array = []; + 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> { + return getIndexPage(page).pipe((indexPage:DataPage) -> { + Promise.whenAll([for (id in indexPage.data) get(id)]).then((data:Array) -> { + return { + page: indexPage.page, + total: indexPage.total, + data: data, + } + }); + }); + } + + public function get(id:I):Promise { + 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 { + 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 { + var stringId = serializeId(id); + var itemData = SharedObject.getLocal('${name}/${stringId}'); + itemData.clear(); + var data:DynamicAccess = indexData.data; + data.remove(stringId); + indexData.flush(); + return Promise.promise(true); + } +} diff --git a/src/haxe/ru/m/data/IDataSource.hx b/src/haxe/ru/m/data/IDataSource.hx index 205e3ff..bfe2980 100644 --- a/src/haxe/ru/m/data/IDataSource.hx +++ b/src/haxe/ru/m/data/IDataSource.hx @@ -2,11 +2,13 @@ package ru.m.data; import promhx.Promise; +typedef Filter = Map; + typedef Page = { var index:Int; var count:Int; - @:optional var order:String; - @:optional var filter:String; + @:optional var order:Array; + @:optional var filter:Filter; } typedef DataPage = { @@ -15,6 +17,10 @@ typedef DataPage = { var data:Array; } -interface IDataSource { - public function getPage(page:Page):Promise>; +interface IDataSource { + public function getIndexPage(page:Page):Promise>; + public function getPage(page:Page):Promise>; + public function get(id:I):Promise; + public function save(item:D):Promise; + public function delete(id:I):Promise; } diff --git a/src/haxe/ru/m/puzzlez/core/GameState.hx b/src/haxe/ru/m/puzzlez/core/GameState.hx index 89e68d7..8a16e7a 100644 --- a/src/haxe/ru/m/puzzlez/core/GameState.hx +++ b/src/haxe/ru/m/puzzlez/core/GameState.hx @@ -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; diff --git a/src/haxe/ru/m/puzzlez/core/GameUtil.hx b/src/haxe/ru/m/puzzlez/core/GameUtil.hx index c90c112..cddc55f 100644 --- a/src/haxe/ru/m/puzzlez/core/GameUtil.hx +++ b/src/haxe/ru/m/puzzlez/core/GameUtil.hx @@ -129,6 +129,7 @@ class GameUtil { } } return { + id: preset.imageId, status: READY, preset: preset, parts: parts, diff --git a/src/haxe/ru/m/puzzlez/storage/GameStorage.hx b/src/haxe/ru/m/puzzlez/storage/GameStorage.hx index e929524..495df5e 100644 --- a/src/haxe/ru/m/puzzlez/storage/GameStorage.hx +++ b/src/haxe/ru/m/puzzlez/storage/GameStorage.hx @@ -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 { - - private static var path = "game_1"; - private var statusData:SharedObject; +@:provide class GameStorage extends DataStorage { + 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> { - 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 { - 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 { - var data:DynamicAccess = statusData.data; - var result:Array = []; - 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 = statusData.data; - data.remove(imageId); - statusData.flush(); - } - - public function clear():Void { - //ToDo: implement me + super( + '${NAME}/${VERSION}', + new DataFilter(item -> ["status" => item.status]), + new Converter(id -> id.toString(), data -> ImageId.fromString(data)) + ); } } diff --git a/src/haxe/ru/m/puzzlez/view/GameListFrame.hx b/src/haxe/ru/m/puzzlez/view/GameListFrame.hx index 5c9fc8d..a41dbd5 100644 --- a/src/haxe/ru/m/puzzlez/view/GameListFrame.hx +++ b/src/haxe/ru/m/puzzlez/view/GameListFrame.hx @@ -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 _: diff --git a/src/haxe/ru/m/puzzlez/view/ImageListFrame.hx b/src/haxe/ru/m/puzzlez/view/ImageListFrame.hx index 03d8023..747257d 100644 --- a/src/haxe/ru/m/puzzlez/view/ImageListFrame.hx +++ b/src/haxe/ru/m/puzzlez/view/ImageListFrame.hx @@ -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 { diff --git a/src/haxe/ru/m/puzzlez/view/PuzzleImageView.hx b/src/haxe/ru/m/puzzlez/view/PuzzleImageView.hx index 9741542..4ed491a 100644 --- a/src/haxe/ru/m/puzzlez/view/PuzzleImageView.hx +++ b/src/haxe/ru/m/puzzlez/view/PuzzleImageView.hx @@ -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; } } diff --git a/src/haxe/ru/m/puzzlez/view/StartFrame.hx b/src/haxe/ru/m/puzzlez/view/StartFrame.hx index 8db0a24..e9914e3 100644 --- a/src/haxe/ru/m/puzzlez/view/StartFrame.hx +++ b/src/haxe/ru/m/puzzlez/view/StartFrame.hx @@ -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(); }