GS2-SkillTree Transaction Actions
Combining actions, and concurrency
The background common to every service is collected in Combining Transaction Actions. Read that first; the rest of this section is what GS2-SkillTree adds to it.
The transaction actions of GS2-SkillTree address one status, identified by the combination of namespace, user, and property. A status records which nodes of the skill tree have been released, and it is rewritten as a whole on every update.
| Operation | Repeated in one transaction | Across a nested transaction | Boundary that separates targets |
|---|---|---|---|
Releasing nodesMarkReleaseByUserId | Combined, with node names concatenated and duplicates removed | Fails | namespace, user, property |
Restraining nodesMarkRestrainByUserId | Combined, with node names concatenated and duplicates removed | Fails | namespace, user, property |
Releasing a node comes with paying whatever that node requires, and restraining it returns that payment. Those payments and returns are issued as a transaction of their own, so the restrictions of whichever services they belong to apply.
The return is derived from the payment: each consume action of the release is turned into the acquire action that undoes it. A reset processes every released node at once, so returns derived from different nodes end up gathered into one transaction. If two nodes take Gs2Mission:ReceiveByUserId for different tasks of the same mission group, for instance, the two returns collide and the restrain or the reset fails.
To release several nodes that do not depend on each other, list them in the nodeModelNames of a single action, or specify several actions and let them be combined; both work.
Releasing a node together with its prerequisite requires putting them in one action. Releasing a node checks that its prerequisites are already released, and that check is made against the status as of the start of the transaction, so splitting them into two actions leaves the release of the prerequisite invisible to the other one.
Releasing and restraining are separate rows sharing a boundary, so they cannot go in one transaction. Split the transaction.
A different property is a different status, so releasing nodes on several properties in one transaction is fine.
Take care with nested transactions
Even two releases on the same status, which would have been combined into one if written side by side, make the transaction fail when one of them arrives from the inside.
If you want to avoid these restrictions
Releasing a node is an acquire action and restraining one is a consume action. Turning acquireActionUseJobQueue on clears collisions between releases, but a release together with a restrain needs enableAtomicCommit turned off. To release a node and its prerequisite together, listing them in one action stays the reliable way whichever setting you use.
Concurrency and retries
Where several requests update the same status at the same time, the one confirmed later returns a conflict (409). Nothing is wrong with the request, so retrying will succeed, and the prerequisites are re-checked against the latest status on the retry.
Releasing nodes on different properties at the same time does not conflict.
Consume Action
Gs2SkillTree:MarkRestrainByUserId
Revert a node to unreleased state by User ID
Validates that the specified nodes can be restrained (no dependent nodes must be in released state), then marks them as unreleased.
Quantity specification supported: NO
Reversible action: YES
| Type | Condition | Required | Default | Value Limits | Description | |
|---|---|---|---|---|---|---|
| namespaceName | string | ✓ | ~ 128 chars | Namespace name Unique Namespace name. Specified using alphanumeric characters, hyphens (-), underscores (_), and periods (.). | ||
| userId | string | ✓ | ~ 128 chars | User ID Specify #{userId} to substitute the currently logged-in user’s ID. | ||
| propertyId | string | ✓ | ~ 1024 chars | Property ID An identifier that allows multiple independent skill tree instances per user. Enables scenarios where a player has separate skill trees for different characters or contexts within the same Namespace. Maximum 1024 characters. | ||
| nodeModelNames | List<string> | ✓ | 1 ~ 1000 items | List of node model names | ||
| timeOffsetToken | string | ~ 1024 chars | Time offset token |
{
"action": "Gs2SkillTree:MarkRestrainByUserId",
"request": {
"namespaceName": "[string]Namespace name",
"userId": "[string]User ID",
"propertyId": "[string]Property ID",
"nodeModelNames": [
"[string]Node Model name"
],
"timeOffsetToken": "[string]Time offset token"
}
}action: Gs2SkillTree:MarkRestrainByUserId
request:
namespaceName: "[string]Namespace name"
userId: "[string]User ID"
propertyId: "[string]Property ID"
nodeModelNames:
- "[string]Node Model name"
timeOffsetToken: "[string]Time offset token"transaction.service("skillTree").consume.mark_restrain_by_user_id({
namespaceName="[string]Namespace name",
userId="[string]User ID",
propertyId="[string]Property ID",
nodeModelNames={
"[string]Node Model name"
},
timeOffsetToken="[string]Time offset token",
})Acquire Action
Gs2SkillTree:MarkReleaseByUserId
Mark a node as released by User ID
Validates that the specified nodes can be released (prerequisite nodes must already be released), then marks them as released.
Quantity specification supported: NO
Reversible action: YES
| Type | Condition | Required | Default | Value Limits | Description | |
|---|---|---|---|---|---|---|
| namespaceName | string | ✓ | ~ 128 chars | Namespace name Unique Namespace name. Specified using alphanumeric characters, hyphens (-), underscores (_), and periods (.). | ||
| userId | string | ✓ | ~ 128 chars | User ID Specify #{userId} to substitute the currently logged-in user’s ID. | ||
| propertyId | string | ✓ | ~ 1024 chars | Property ID An identifier that allows multiple independent skill tree instances per user. Enables scenarios where a player has separate skill trees for different characters or contexts within the same Namespace. Maximum 1024 characters. | ||
| nodeModelNames | List<string> | ✓ | 1 ~ 1000 items | List of Node Model names | ||
| timeOffsetToken | string | ~ 1024 chars | Time offset token |
{
"action": "Gs2SkillTree:MarkReleaseByUserId",
"request": {
"namespaceName": "[string]Namespace name",
"userId": "[string]User ID",
"propertyId": "[string]Property ID",
"nodeModelNames": [
"[string]Node Model name"
],
"timeOffsetToken": "[string]Time offset token"
}
}action: Gs2SkillTree:MarkReleaseByUserId
request:
namespaceName: "[string]Namespace name"
userId: "[string]User ID"
propertyId: "[string]Property ID"
nodeModelNames:
- "[string]Node Model name"
timeOffsetToken: "[string]Time offset token"transaction.service("skillTree").acquire.mark_release_by_user_id({
namespaceName="[string]Namespace name",
userId="[string]User ID",
propertyId="[string]Property ID",
nodeModelNames={
"[string]Node Model name"
},
timeOffsetToken="[string]Time offset token",
})