Skip to content

Commit 8af8f26

Browse files
Wip rework save & load actions + new time expression
1 parent b8080f7 commit 8af8f26

File tree

4 files changed

+126
-81
lines changed

4 files changed

+126
-81
lines changed

Extensions/SaveState/JsExtension.js

Lines changed: 54 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -29,66 +29,87 @@ module.exports = {
2929
.setCategory('Save & Load');
3030
extension
3131
.addInstructionOrExpressionGroupMetadata(_('Save State'))
32-
.setIcon('JsPlatform/Extensions/snapshotsave.svg');
32+
.setIcon('res/actions/saveDown.svg');
3333

3434
// TODO: Split save action and load action into 2 different instructions to avoid
3535
// having optional and empty parameters.
3636
extension
3737
.addAction(
38-
'SaveGameSnapshot',
39-
_('Save game'),
40-
_(
41-
'Takes a snapshot of the game and save it to a variable or device storage.'
42-
),
43-
_(
44-
'Save the game to variable _PARAM1_ or to storage under key _PARAM2_.'
45-
),
38+
'SaveGameSnapshotToVariable',
39+
_('Save game to a variable'),
40+
_('Takes a snapshot of the game and save it to a variable.'),
41+
_('Save the game in variable _PARAM1_'),
4642
'',
4743
'res/actions/saveDown.svg',
4844
'res/actions/saveDown.svg'
4945
)
5046
.addCodeOnlyParameter('currentScene', '')
51-
.addParameter(
52-
'variable',
53-
_('Variable to store the save to (optional)'),
47+
.addParameter('variable', _('Variable to store the save to'), '', false)
48+
.getCodeExtraInformation()
49+
.setIncludeFile('Extensions/SaveState/savestatetools.js')
50+
.setFunctionName('gdjs.saveState.saveVariableGameSnapshot');
51+
52+
extension
53+
.addAction(
54+
'SaveGameSnapshotToStorage',
55+
_('Save game to device storage'),
56+
_('Takes a snapshot of the game and save it to device storage.'),
57+
_('Save the game to device storage under key _PARAM1_'),
5458
'',
55-
true
59+
'res/actions/saveDown.svg',
60+
'res/actions/saveDown.svg'
5661
)
57-
.addParameter('string', _('Storage key to save to (optional)'), '', true)
62+
.addCodeOnlyParameter('currentScene', '')
63+
.addParameter('string', _('Storage key to save to'), '', false)
5864
.getCodeExtraInformation()
5965
.setIncludeFile('Extensions/SaveState/savestatetools.js')
60-
.setFunctionName('gdjs.saveState.saveGameSnapshot');
66+
.setFunctionName('gdjs.saveState.saveStorageGameSnapshot');
6167

6268
extension
6369
.addAction(
64-
'LoadGameSnapshot',
65-
_('Load game'),
66-
_('Load game from snapshot save from a variable or storage.'),
67-
_(
68-
'Load the game from variable _PARAM1_ or from device storage under key _PARAM2_.'
69-
),
70+
'LoadGameSnapshotFromVariable',
71+
_('Load game from variable'),
72+
_('Load game from a variable save snapshot.'),
73+
_('Load the game from variable _PARAM1_'),
7074
'',
7175
'res/actions/saveUp.svg',
7276
'res/actions/saveUp.svg'
7377
)
7478
.addCodeOnlyParameter('currentScene', '')
75-
.addParameter(
76-
'variable',
77-
_('Variable to load game from (optional)'),
78-
'',
79-
true
80-
)
81-
.addParameter(
82-
'string',
83-
_('Storage key to load game from (optional)'),
79+
.addParameter('variable', _('Variable to load the game from'), '', false)
80+
.getCodeExtraInformation()
81+
.setIncludeFile('Extensions/SaveState/savestatetools.js')
82+
.setFunctionName('gdjs.saveState.loadGameFromVariableSnapshot');
83+
84+
extension
85+
.addAction(
86+
'LoadGameSnapshotFromStorage',
87+
_('Load game from storage'),
88+
_('Load game from storage save snapshot.'),
89+
_('Load the game from device storage under key _PARAM1_.'),
8490
'',
85-
true
91+
'res/actions/saveUp.svg',
92+
'res/actions/saveUp.svg'
8693
)
94+
.addCodeOnlyParameter('currentScene', '')
95+
.addParameter('string', _('Storage key to load the game from'), '', false)
8796
.getCodeExtraInformation()
8897
.setIncludeFile('Extensions/SaveState/savestatetools.js')
89-
.setFunctionName('gdjs.saveState.loadGameFromSnapshot');
98+
.setFunctionName('gdjs.saveState.loadGameFromStorageSnapshot');
9099

91-
// TODO: Add condition and expression to get the last save creation datetime.
100+
extension
101+
.addExpressionAndConditionAndAction(
102+
'number',
103+
'SecondsSinceLastSave',
104+
_('Seconds since last save'),
105+
_('the number of seconds since the last save'),
106+
_('the number of seconds since the last save'),
107+
'',
108+
'res/actions/saveDown.svg'
109+
)
110+
.useStandardParameters('number', gd.ParameterOptions.makeNewOptions())
111+
.setFunctionName('gdjs.saveState.getSecondsSinceLastSave')
112+
.setGetter('gdjs.saveState.getSecondsSinceLastSave');
92113

93114
return extension;
94115
},

Extensions/SaveState/savestatetools.ts

Lines changed: 62 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,97 @@
11
namespace gdjs {
22
export namespace saveState {
3-
export const INDEXED_DB_NAME: string = 'gd-game-snapshot-saves';
4-
export const INDEXED_DB_KEY: string = 'game_save'; // TODO: use game id to change the key.
5-
export const INDEXED_DB_OBJECT_STORE: string = 'saves'; // TODO: should a store be used per game on the user device?
3+
export const INDEXED_DB_NAME: string = 'gdevelop-game-saves';
64

7-
export const saveGameSnapshot = async function (
8-
currentScene: RuntimeScene,
9-
sceneVar?: gdjs.Variable,
10-
storageName?: string
11-
) {
12-
let allSyncData: GameSaveState = {
5+
let lastSaveTime: number | null = null;
6+
7+
export const getIndexedDbObjectStore = () => {
8+
const gameId = gdjs.projectData.properties.projectUuid;
9+
return `game-saves-${gameId}`;
10+
};
11+
12+
export const getIndexedDbStorageKey = (key: string) => {
13+
return `save-${key}`;
14+
};
15+
16+
export const getSecondsSinceLastSave = (): number => {
17+
if (!lastSaveTime) return -1;
18+
return Math.floor((Date.now() - lastSaveTime) / 1000);
19+
};
20+
21+
const getGameSaveState = (runtimeScene: RuntimeScene) => {
22+
const gameSaveState: GameSaveState = {
1323
gameNetworkSyncData: {},
1424
layoutNetworkSyncDatas: [],
1525
};
16-
const gameData = currentScene
26+
const gameData = runtimeScene
1727
.getGame()
1828
.getNetworkSyncData({ forceSyncEverything: true });
19-
const sceneStack = currentScene.getGame()._sceneStack._stack;
20-
allSyncData.gameNetworkSyncData = gameData || {};
29+
const sceneStack = runtimeScene.getGame()._sceneStack._stack;
30+
gameSaveState.gameNetworkSyncData = gameData || {};
2131
sceneStack.forEach((scene, index) => {
2232
const sceneDatas = (scene.getNetworkSyncData({
2333
forceSyncEverything: true,
2434
}) || []) as LayoutNetworkSyncData;
2535

26-
allSyncData.layoutNetworkSyncDatas[index] = {
36+
gameSaveState.layoutNetworkSyncDatas[index] = {
2737
sceneData: {} as LayoutNetworkSyncData,
2838
objectDatas: {},
2939
};
30-
allSyncData.layoutNetworkSyncDatas[index].sceneData = sceneDatas;
40+
gameSaveState.layoutNetworkSyncDatas[index].sceneData = sceneDatas;
3141
const sceneRuntimeObjects = scene.getAdhocListOfAllInstances();
3242
const syncOptions: GetNetworkSyncDataOptions = {
3343
forceSyncEverything: true,
3444
};
3545
for (const key in sceneRuntimeObjects) {
3646
if (sceneRuntimeObjects.hasOwnProperty(key)) {
3747
const object = sceneRuntimeObjects[key];
38-
const syncData = object.getNetworkSyncData(syncOptions);
39-
allSyncData.layoutNetworkSyncDatas[index].objectDatas[object.id] =
40-
syncData;
48+
const objectSyncData = object.getNetworkSyncData(syncOptions);
49+
gameSaveState.layoutNetworkSyncDatas[index].objectDatas[object.id] =
50+
objectSyncData;
4151
}
4252
}
4353
});
54+
return gameSaveState;
55+
};
56+
57+
export const saveVariableGameSnapshot = async function (
58+
currentScene: RuntimeScene,
59+
sceneVar: gdjs.Variable
60+
) {
61+
const gameSaveState = getGameSaveState(currentScene);
62+
sceneVar.fromJSObject(gameSaveState);
63+
lastSaveTime = Date.now();
64+
};
4465

45-
// TODO: Store the save creation datetime.
46-
if (sceneVar && sceneVar !== gdjs.VariablesContainer.badVariable) {
47-
sceneVar.fromJSObject(allSyncData);
48-
} else {
49-
await gdjs.saveToIndexedDB(
50-
INDEXED_DB_NAME,
51-
INDEXED_DB_OBJECT_STORE,
52-
storageName || INDEXED_DB_KEY,
53-
allSyncData
54-
);
55-
}
66+
export const saveStorageGameSnapshot = async function (
67+
currentScene: RuntimeScene,
68+
storageKey: string
69+
) {
70+
const gameSaveState = getGameSaveState(currentScene);
71+
await gdjs.saveToIndexedDB(
72+
INDEXED_DB_NAME,
73+
getIndexedDbObjectStore(),
74+
getIndexedDbStorageKey(storageKey),
75+
gameSaveState
76+
);
77+
lastSaveTime = Date.now();
78+
};
79+
80+
export const loadGameFromVariableSnapshot = async function (
81+
currentScene: RuntimeScene,
82+
variable: gdjs.Variable
83+
) {
84+
currentScene.requestLoadSnapshot({
85+
loadVariable: variable,
86+
});
5687
};
5788

58-
export const loadGameFromSnapshot = async function (
89+
export const loadGameFromStorageSnapshot = async function (
5990
currentScene: RuntimeScene,
60-
sceneVar?: gdjs.Variable,
61-
storageName?: string
91+
storageName: string
6292
) {
6393
currentScene.requestLoadSnapshot({
64-
loadVariable:
65-
sceneVar && sceneVar !== gdjs.VariablesContainer.badVariable
66-
? sceneVar
67-
: null,
68-
loadStorageName: storageName || INDEXED_DB_KEY,
94+
loadStorageName: storageName,
6995
});
7096
};
7197
}

GDJS/Runtime/runtimescene.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ namespace gdjs {
77
const logger = new gdjs.Logger('RuntimeScene');
88
const setupWarningLogger = new gdjs.Logger('RuntimeScene (setup warnings)');
99

10+
export type LoadRequestOptions = {
11+
loadStorageName?: string;
12+
loadVariable?: gdjs.Variable;
13+
};
14+
1015
/**
1116
* A scene being played, containing instances of objects rendered on screen.
1217
*/

GDJS/Runtime/scenestack.ts

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -117,28 +117,21 @@ namespace gdjs {
117117
currentScene.requestLoadSnapshot(null);
118118

119119
if (loadRequestOptions.loadVariable) {
120-
if (
121-
loadRequestOptions.loadVariable !==
122-
gdjs.VariablesContainer.badVariable
123-
) {
124-
logger.error('Requested loading save from wrongly defined variable.');
125-
}
126120
const saveState =
127121
loadRequestOptions.loadVariable.toJSObject() as GameSaveState;
128122
try {
129123
this._loadGameFromSave(saveState);
130124
} catch (error) {
131125
logger.error('Error loading from variable:', error);
132126
}
133-
} else {
134-
const storageKey =
135-
loadRequestOptions.loadStorageName || gdjs.saveState.INDEXED_DB_KEY;
136-
127+
} else if (loadRequestOptions.loadStorageName) {
137128
gdjs
138129
.loadFromIndexedDB(
139130
gdjs.saveState.INDEXED_DB_NAME,
140-
gdjs.saveState.INDEXED_DB_OBJECT_STORE,
141-
storageKey
131+
gdjs.saveState.getIndexedDbObjectStore(),
132+
gdjs.saveState.getIndexedDbStorageKey(
133+
loadRequestOptions.loadStorageName
134+
)
142135
)
143136
.then((jsonData) => {
144137
const saveState = jsonData as GameSaveState;

0 commit comments

Comments
 (0)