Skip to content

Commit

Permalink
new save load tutorial
Browse files Browse the repository at this point in the history
  • Loading branch information
Frotty committed Dec 4, 2021
1 parent 4d37df7 commit 940f488
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 51 deletions.
6 changes: 3 additions & 3 deletions _doc/tutorials/saveload.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
---
title: Save and Load
excerpt: Learn how to use Wurst's codeless save and load API
date: 2021-10-06
date: 2021-12-04
icon:
type: fa
name: fa-save
color: blue
layout: tutorialdoc
sections:
- /tutorials/saveload/highlevel
- /tutorials/saveload/lowlevel
- /tutorials/saveload/saveload
- /tutorials/saveload/serializable
---
42 changes: 0 additions & 42 deletions _doc/tutorials/saveload/lowlevel.md

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: High level API
title: Save Load API
sections:
- Welcome
- Behind the scenes
Expand All @@ -15,7 +15,7 @@ Import the `SaveLoadData` package to use the following code examples.

## Behind the scenes

Internally the save load packages make use of the preload exploit, which is now officially endorsed by blizzard to save and load files from the CustomMapData folder in your Warcraft III Document root.
Internally the save load packages make use of the preload exploit, which is now officially endorsed by blizzard to save and load files from the `CustomMapData` folder in your Warcraft III Document root.
Data loaded from a specific player is therefore only loaded on that player's machine and needs to be synchronized with all other players before it can be used in a synchronous environment.
Wurst wraps all this behind a simple API, so you don't need to worry about the details.

Expand All @@ -26,7 +26,7 @@ Existing file contents will be overriden.
Since saving does not require synchronizing, it is a simple, blocking operation.

```wurst
init
function savePlayerData()
players[0].saveData("MyFileName", "someDataString")
```

Expand All @@ -40,17 +40,17 @@ Loading may also fail if the file is empty, corrupted, or the player disconnects
If the status is `SUCCESS`, the `data` parameter will contain the synchronized version of the file's contents, which you can immediately use in a synchronous context.

```wurst
init
function loadPlayerData()
players[0].loadData("MyFileName") (status, data) ->
if status == LoadStatus.SUCCESS
Log.info("Loaded: " + data.readStringUnsafe())
Log.info("Loaded: " + data.getUnsafeString())
else
// some error handling
```

## Limitations and Chunking

As you can see we used `.readStringUnsafe()` in the example above. It is unsafe because the maximum length of a `string` in Jass is capped at 1024 bytes without and 4099 characters with concatenation. This would not only limit how much data we can load/save, but also complicate the usage in general.
As you can see we used `.getUnsafeString()` in the example above. It is unsafe because the maximum length of a `string` in Jass is capped at 1024 bytes without and 4099 characters with concatenation. This would not only limit how much data we can load/save, but also complicate the usage in general.
1024 bytes also doesn't equal to 1024 characters, because certain characters (unicode) take up more than 1 byte.

Thus it is generally recommended to use a `ChunkedString` for any data above around 500 characters. The `ChunkedString` splits big strings into smaller chunks, which can then be acceessed seperately. The save functions are overloaded to allow string or ChunkedString input.
104 changes: 104 additions & 0 deletions _doc/tutorials/saveload/serializable.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
---
title: Serializable
sections:
- Motivation
- Usage
- Complete Example
---

## Motivation

Of course usually we don't want to save just strings, but data that is stored in some object.
For this purpose there is the `abstract class Serializable` for simple saving and loading of primitive attribute values.
The `serialize()` function returns a `ChunkedString`, which then can be passed to the data save function from above.


## Usage

Import `Serializable` Make your data class extend `Serializable` and implement the required functions.

```wurst
import Serializable
class MyClass extends Serializable
var amount = 0
override function serializeProperties()
override function deserializeProperties()
```

### Saving Attributes

Inside the `serializeProperties` register all attributes that should be serialized using `addProperty`.

```wurst
override function serializeProperties()
addProperty("amount", amount)
```

### Loading attributes

Every serialized attribute should also be deserialized again in the `deserializeProperties` function using the appropriate `getXXProperty` function and assigned to the attribute.
Make sure to use the same name used for saving.

```wurst
override function deserializeProperties()
amount = getIntProperty("amount")
```

### Serializing

Serialize the data class object to a `ChunkedString` using `serialize`. You can pass this chunked string directly to the save functions from above.

```wurst
let saveData = new MyClass()
saveData.amount = 1337
let result = saveData.serialize()
```

### Deserializing

To load from a `ChunkedString`, which could be obtained by loading using the function above, use `deserialize`.

```wurst
let loadedData = new MyClass()
loadedData.deserialize(inputChunkedString)
```

## Complete Example

Find a full working example below. The first time you run the map a new save file will be created saving the value `1337` in the `amount` attribute.
The second time you run the map it will load the existing save file and assign `1337` to the loaded object's `amount` variable, which is printed on the screen.

```wurst
package SerTest
import Serializable
import SaveLoadData
import ClosureTimers
constant FILE_NAME = "MyFileName"
class MyClass extends Serializable
var amount = 0
override function serializeProperties()
addProperty("amount", amount)
override function deserializeProperties()
amount = getIntProperty("amount")
init
doAfter(1.) ->
players[0].loadData(FILE_NAME) (status, data) ->
if status == LoadStatus.SUCCESS
let loadedData = new MyClass()..deserialize(data)
print("loaded: " + loadedData.amount.toString())
else
let saveData = new MyClass()
saveData.amount = 1337
let saveString = saveData.serialize()
players[0].saveData(FILE_NAME, saveString)
print("created new save file")
```

0 comments on commit 940f488

Please sign in to comment.