User Tools

Site Tools


tutorial:transfer-api_transactions

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
tutorial:transfer-api_transactions [2023/07/19 13:43] – [Technical details] technici4ntutorial:transfer-api_transactions [2023/07/19 14:00] (current) – [Nested transactions] technici4n
Line 23: Line 23:
 </code> </code>
  
-While this seems correct, it is possible that ''%%doConsumeWater%%'' succeeds but ''%%doConsumeLava%%'' fails. For example, if we need to use some power to consume a fluid. Consider the following sequence: Our tank has water, lava, and 1 unit of power. ''%%canConsumeWater%%'': we have water and 1 unit of power -> ''%%true%%''''%%canConsumeLava%%'': we have lava and 1 unit of power -> ''%%true%%''''%%doConsumeWater%%'': we have water and 1 unit of power -> OK. This time we consume the power. ''%%doConsumeLava%%'': we have lava but we do not have power anymore.+While this seems correct, it is possible that ''doConsumeWater'' succeeds but ''doConsumeLava'' fails. For example, if we need to use some power to consume a fluid. Consider the following sequence: 
 +  * Our tank has water, lava, and 1 unit of power. 
 +  * ''canConsumeWater'': we have water and 1 unit of power -> ''true''. 
 +  * ''canConsumeLava'': we have lava and 1 unit of power -> ''true''. 
 +  * ''doConsumeWater'': we have water and 1 unit of power -> OK. This time we consume the power. 
 +  * ''doConsumeLava'': we have lava but we do not have power anymore.
  
 We are now in a broken state where water was consumed but lava cannot be consumed. Either we abort the process and accept that water was deleted, or we continue the process and we still produce the obsidian. Either we delete resources that we shouldn’t, or we create resources that we shouldn’t. We are now in a broken state where water was consumed but lava cannot be consumed. Either we abort the process and accept that water was deleted, or we continue the process and we still produce the obsidian. Either we delete resources that we shouldn’t, or we create resources that we shouldn’t.
Line 92: Line 97:
 </code> </code>
  
-Here is how transactions can be visualized: Opening a new transaction creates a new copy of the state. From now on, that copy is modified. Aborting a transaction discards that copy. Back to the original state. Committing a transaction replaces the original state by the modified copy. From now on, this is the new state.+Here is how transactions can be visualized: 
 +  * Opening a new transaction creates a new copy of the state. From now on, that copy is modified. 
 +  * Aborting a transaction discards that copy. Back to the original state. 
 +  * Committing a transaction replaces the original state by the modified copy. From now on, this is the new state.
  
-We can represent this in a graph with branches: Any modification operates on the top branch. Opening a new transaction creates a new branch. Aborting a transaction discards the top branch. Committing a transaction merges the top branch into the branch below it.+We can represent this in a graph with branches: 
 +  * Any modification operates on the top branch. 
 +  * Opening a new transaction creates a new branch. 
 +  * Aborting a transaction discards the top branch. 
 +  * Committing a transaction merges the top branch into the branch below it.
  
 Here is the branching graph for that first example: Here is the branching graph for that first example:
  
-{{https://i.imgur.com/qW0ROX3.png}}+{{:tutorial:transaction_graph_1.png?nolink&400|}}
  
 ==== Nested transactions ==== ==== Nested transactions ====
Line 140: Line 152:
 Here is the corresponding graph: Here is the corresponding graph:
  
-{{https://i.imgur.com/n4jZ9FH.png}}+{{:tutorial:transaction_graph_2.png?nolink|}}
  
 === Transaction vs TransactionContext === === Transaction vs TransactionContext ===
  
-You might have noticed that sometimes we use ''%%Transaction%%'' and sometimes we use ''%%TransactionContext%%''. The former has some additional functions that are only relevant to the code that opened the transaction. The rule of thumb is: Use ''%%Transaction%%'' in code that opens and closes transactions. Use ''%%TransactionContext%%'' in code that implements transaction-aware operations.+You might have noticed that sometimes we use ''Transaction'' and sometimes we use ''TransactionContext''. The former has some additional functions that are only relevant to the code that opened the transaction. The rule of thumb is: 
 +  * Use ''Transaction'' in code that opens and closes transactions. 
 +  * Use ''TransactionContext'' in code that implements transaction-aware operations.
  
 ==== Implementing support for transactions ==== ==== Implementing support for transactions ====
  
-In this section we explain how ''%%TransactionalInteger%%'' can be written. For that, we will use ''%%SnapshotParticipant%%'' that will do the heavy lifting for us. **ALWAYS use ''%%SnapshotParticipant%%'', NEVER use the raw primitives directly (''%%TransactionContext#addCallback%%'' and ''%%TransactionContext#addOuterCallback%%'').**+In this section we explain how ''TransactionalInteger'' can be written. For that, we will use ''SnapshotParticipant'' that will do the heavy lifting for us. **ALWAYS use ''SnapshotParticipant'', NEVER use the raw primitives directly (''TransactionContext#addCallback'' and ''TransactionContext#addOuterCallback'').**
  
-A ''%%SnapshotParticipant%%'' saves copies of its internal state and restores them when required. These copies are referred to as “snapshots”, hence the name. Using a ''%%SnapshotParticipant%%'' is generally quite simple: 1. Choose a data type to represent copies of internal state. Usually this will be a record. Here we will use ''%%Integer%%''2. Extend ''%%SnapshotParticipant<internal state>%%''. Here we will add ''%%extends SnapshotParticipant<Integer>%%'' to our class. 3. Implement functions to create copies of the internal state, and restore copies thereof - respectively ''%%createSnapshot%%'' and ''%%readSnapshot%%''4. Call ''%%updateSnapshots(transaction)%%'' before any change to the internal state.+A ''SnapshotParticipant'' saves copies of its internal state and restores them when required. These copies are referred to as “snapshots”, hence the name. Using a ''SnapshotParticipant'' is generally quite simple: 
 +  - Choose a data type to represent copies of internal state. Usually this will be a record. Here we will use ''Integer''. 
 +  - Extend ''SnapshotParticipant<internal state>''. Here we will add ''extends SnapshotParticipant<Integer>'' to our class. 
 +  - Implement functions to create copies of the internal state, and restore copies thereof - respectively ''createSnapshot'' and ''readSnapshot''. 
 +  - Call ''updateSnapshots(transaction)'' before any change to the internal state.
  
 Let’s start with the following template: Let’s start with the following template:
Line 189: Line 207:
 </code> </code>
  
-Once this is done, we can implement ''%%increment%%'' as follows. Always remember to call ''%%updateSnapshots%%'' **before the internal state is modified**.+Once this is done, we can implement ''increment'' as follows. Always remember to call ''updateSnapshots'' **before the internal state is modified**.
  
 <code java> <code java>
Line 204: Line 222:
 ==== Correctly saving changes ==== ==== Correctly saving changes ====
  
-If we need to perform an operation after a change, we can override ''%%onFinalCommit%%''. It will only be called when the ''%%SnapshotParticipant%%'' is involved in a transaction that is committed. In other words, it will only be called if some modification made its way back to the bottom of the branching graph.+If we need to perform an operation after a change, we can override ''onFinalCommit''. It will only be called when the ''SnapshotParticipant'' is involved in a transaction that is committed. In other words, it will only be called if some modification made its way back to the bottom of the branching graph.
  
-For example, if we modified the internal state of a block entity, we should make sure to call ''%%markDirty%%'' at the end:+For example, if we modified the internal state of a block entity, we should make sure to call ''markDirty'' at the end:
  
 <code java> <code java>
Line 218: Line 236:
 ==== Technical details ==== ==== Technical details ====
  
-//This section explains the details of how the ''%%SnapshotParticipant%%'' system works. Feel free to skip it if this is not interesting to you.//+//This section explains the details of how the ''SnapshotParticipant'' system works. Feel free to skip it if this is not interesting to you.//
  
-The goal of ''%%SnapshotParticipant%%'' is to bridge the gap between the transaction programming model (opening a transaction copies the game state, etc…) and an efficient implementation.+The goal of ''SnapshotParticipant'' is to bridge the gap between the transaction programming model (opening a transaction copies the game state, etc…) and an efficient implementation.
  
-The ''%%SnapshotParticipant%%'' stores up to one snapshot per transaction - tracking the state to which it should revert if said transaction were aborted. To minimize data copies, the snapshots are only created lazily.+The ''SnapshotParticipant'' stores up to one snapshot per transaction - tracking the state to which it should revert if said transaction were aborted. To minimize data copies, the snapshots are only created lazily.
  
-For performance reasons, aborting or committing a transaction just runs a list of actions, but does not worry about more complex things such as nesting or state copies. See ''%%Transaction#addCloseCallback%%''. All of the state management logic is thus part of the ''%%SnapshotParticipant%%'' itself.+For performance reasons, aborting or committing a transaction just runs a list of actions, but does not worry about more complex things such as nesting or state copies. See ''Transaction#addCloseCallback''. All of the state management logic is thus part of the ''SnapshotParticipant'' itself.
  
-Now that we have been through this background knowledge, here is how the ''%%SnapshotParticipant%%'' operates: When ''%%updateSnapshots%%'' is called: If we already have a snapshot for this transaction, do nothing. Otherwise call ''%%createSnapshot%%'' to save a snapshot, and add a close callback. When a transaction is aborted: We are guaranteed to have a snapshot for that transaction due to how state is managed. Call ''%%readSnapshot%%'' to revert the state changes. When a transaction is committed: If this is an outer (= not nested) transaction, the change is confirmed. We know that something probably changed, otherwise we would not have a registered close callback. Call ''%%addOuterCloseCallback%%''. The callback will call ''%%onFinalCommit%%''If this is a nested transaction, we need to deal with the snapshot: If the parent transaction already has an older snapshot, discard the more recent snapshot. Otherwise the snapshot is moved to the parent transaction.+Now that we have been through this background knowledge, here is how the ''SnapshotParticipant'' operates: 
 +  * When ''updateSnapshots'' is called: 
 +    * If we already have a snapshot for this transaction, do nothing. 
 +    * Otherwise call ''createSnapshot'' to save a snapshot, and add a close callback. 
 +  * When a transaction is aborted: 
 +    * We are guaranteed to have a snapshot for that transaction due to how state is managed. 
 +    * Call ''readSnapshot'' to revert the state changes. 
 +  * When a transaction is committed: 
 +    * If this is an outer (= not nested) transaction, the change is confirmed. 
 +      * We know that something probably changed, otherwise we would not have a registered close callback. 
 +      * Call ''addOuterCloseCallback''. The callback will call ''onFinalCommit''. 
 +    * If this is a nested transaction, we need to deal with the snapshot: 
 +      * If the parent transaction already has an older snapshot, discard the more recent snapshot. 
 +      * Otherwise the snapshot is moved to the parent transaction.
  
 Hopefully that gives an overview of what is happening under the hood. You should now be ready to read [[https://github.com/FabricMC/fabric/blob/1.20.1/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/api/transfer/v1/transaction/base/SnapshotParticipant.java|the source code of SnapshotParticipant]]. Hopefully that gives an overview of what is happening under the hood. You should now be ready to read [[https://github.com/FabricMC/fabric/blob/1.20.1/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/api/transfer/v1/transaction/base/SnapshotParticipant.java|the source code of SnapshotParticipant]].
  
tutorial/transfer-api_transactions.1689774204.txt.gz · Last modified: 2023/07/19 13:43 by technici4n