GS2-Quest 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-Quest adds to it.
GS2-Quest provides two transaction actions, both addressing the progress of one user, identified by the combination of namespace and user. There is one progress per user, which is what limits a player to one quest at a time.
| Operation | Repeated in one transaction | Across a nested transaction | Boundary that separates targets |
|---|---|---|---|
Creating a progressCreateProgressByUserId | Rejected when the transaction is issued, even if the requests are identical | Fails | namespace, user |
Discarding a progressDeleteProgressByUserId | Rejected when the transaction is issued, even if the requests are identical | Combined into one | namespace, user |
Creating and discarding are separate rows sharing a boundary, so they cannot go in one transaction whether or not a progress exists. The transaction is rejected when it is issued.
Creating a progress fails where another progress already exists. That check is made against the state at the start of the transaction, so where enableAtomicCommit is on, quite apart from the collision, a transaction cannot discard a progress and then start a new quest. Split the transaction.
Because there is only one progress per user, placing two creations for the same user in one transaction is a contradictory instruction in itself. There is nothing to decide which of them should run, so it is rejected when the transaction is issued, even where the questModelId, the force flag and the settings are all identical. Treat “buy three and start the quest three times” as something that cannot be expressed. The same goes for two discards: however many are placed, the transaction is rejected when it is issued.
Quest rewards are issued separately
Completing or failing a quest is not a transaction action. When a quest ends, GS2-Quest issues its rewards as a transaction of its own, so the restrictions of whichever services those rewards belong to apply, and so does the section below.
That transaction always contains a discard of the progress. Where enableAtomicCommit is on, a reward therefore cannot hold a CreateProgressByUserId against the same namespace to start the next quest automatically: even a single creation sits together with the discard, and the transaction is rejected when it is issued. Split the transaction where you want to chain quests.
Take care with nested transactions
A progress created from the inside collides with a progress discarded from the outside, and the transaction fails. Two creations write to the same progress twice and fail as well; creating a progress writes a fresh identifier and a fresh random seed every time, so the values written never match even where the requests are identical, and the combining that applies to matching values does not apply here. Only two discards do not fail: deleting the same progress is idempotent, so they are combined into one even across a nest.
If you want to avoid these restrictions
Creating a progress is an acquire action and discarding one is a consume action. Two creations, or two discards, are checked when the transaction is issued, before either setting is applied, so neither turning acquireActionUseJobQueue on nor turning enableAtomicCommit off clears them; reduce them to one, or split the transaction.
A creation together with a discard is cleared by turning enableAtomicCommit off. Consume actions then run before acquire actions, so discarding and starting a new quest in one transaction starts to work as well. acquireActionUseJobQueue separates acquire actions from each other, so do not rely on it here: where there is only one acquire action it is not put on the queue, and it stays in the same transaction as the discard.
Concurrency and retries
Where several requests create or discard the same user’s progress at the same time, the one confirmed later returns a conflict (409). Retrying re-evaluates against the latest state, so it succeeds if the progress is in the expected state and returns an error if another quest has already started.
Consume Action
Gs2Quest:DeleteProgressByUserId
Delete Quest Progress by User ID
Deletes the current quest progress for the specified user. This cancels the quest in progress and allows starting a new quest.
Quantity specification supported: NO
Reversible action: NO
| 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. | ||
| timeOffsetToken | string | ~ 1024 chars | Time offset token |
{
"action": "Gs2Quest:DeleteProgressByUserId",
"request": {
"namespaceName": "[string]Namespace name",
"userId": "[string]User ID",
"timeOffsetToken": "[string]Time offset token"
}
}action: Gs2Quest:DeleteProgressByUserId
request:
namespaceName: "[string]Namespace name"
userId: "[string]User ID"
timeOffsetToken: "[string]Time offset token"transaction.service("quest").consume.delete_progress_by_user_id({
namespaceName="[string]Namespace name",
userId="[string]User ID",
timeOffsetToken="[string]Time offset token",
})Acquire Action
Gs2Quest:CreateProgressByUserId
Create Quest Progress by User ID
Creates a quest progress by looking up the quest model, performing a lottery on the quest content, and starting the quest. If a quest is already in progress, it returns an error unless the force flag is set.
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. | ||
| questModelId | string | ✓ | ~ 1024 chars | Quest Model GRN to Start | ||
| force | bool | false | If have a quest already started, you can discard it and start it | |||
| config | List<Config> | [] | 0 ~ 32 items | Configuration values applied to transaction variables | ||
| timeOffsetToken | string | ~ 1024 chars | Time offset token |
{
"action": "Gs2Quest:CreateProgressByUserId",
"request": {
"namespaceName": "[string]Namespace name",
"userId": "[string]User ID",
"questModelId": "[string]Quest Model GRN to Start",
"force": "[bool]If have a quest already started, you can discard it and start it",
"config": [
{
"key": "[string]Name",
"value": "[string]Value"
}
],
"timeOffsetToken": "[string]Time offset token"
}
}action: Gs2Quest:CreateProgressByUserId
request:
namespaceName: "[string]Namespace name"
userId: "[string]User ID"
questModelId: "[string]Quest Model GRN to Start"
force: "[bool]If have a quest already started, you can discard it and start it"
config:
- key: "[string]Name"
value: "[string]Value"
timeOffsetToken: "[string]Time offset token"transaction.service("quest").acquire.create_progress_by_user_id({
namespaceName="[string]Namespace name",
userId="[string]User ID",
questModelId="[string]Quest Model GRN to Start",
force="[bool]If have a quest already started, you can discard it and start it",
config={
{
key="[string]Name",
value="[string]Value"
}
},
timeOffsetToken="[string]Time offset token",
})