Files
questshock/modding/hacked/ss1/edit/undoable/cmd/Transaction.go
T

158 lines
5.0 KiB
Go

package cmd
import (
"fmt"
"github.com/inkyblackness/hacked/ss1/world"
)
// Task represents a modification on given modder.
type Task func(modder world.Modder) error
// TransactionError is returned from a transaction execution if a task failed.
type TransactionError struct {
// Name is that of the transaction, if it was set.
Name string
// Index identifies the list index of tasks.
Index int
// Nested is the error returned from the task. In case of nested commands, this may be another TransactionError.
Nested error
}
// Error returns the path of the transaction task, combined with the nested error text.
func (err TransactionError) Error() string {
return fmt.Sprintf("%s[%d]: %v", err.Name, err.Index, err.Nested)
}
// Unwrap returns the nested error, to support diving into nested errors.
func (err TransactionError) Unwrap() error {
return err.Nested
}
// Transaction is a named list of forward and reverse tasks.
// Transaction provides execution of the tasks as a Command.
type Transaction struct {
Name string
Forward []Task
Reverse []Task
}
// Do executes the forward tasks in series. The first task to return an error aborts the iteration.
// A TransactionError is returned in case of aborted iteration.
func (txn Transaction) Do(modder world.Modder) error {
return txn.perform(txn.Forward, modder)
}
// Undo executes the reverse tasks in series. The first task to return an error aborts the iteration.
// A TransactionError is returned in case of aborted iteration.
func (txn Transaction) Undo(modder world.Modder) error {
return txn.perform(txn.Reverse, modder)
}
func (txn Transaction) perform(tasks []Task, modder world.Modder) error {
for i, task := range tasks {
err := task(modder)
if err != nil {
return TransactionError{
Name: txn.Name,
Index: i,
Nested: err,
}
}
}
return nil
}
// TransactionModifier is a function to change a transaction while it is being built.
type TransactionModifier func(*Transaction) error
// Registry allows to build commands as transactions with modifier functions.
type Registry interface {
Commander
Register(modifier ...TransactionModifier) error
}
// TransactionBuilder provides a way to register a command at a commander with
// possibly nested actions.
type TransactionBuilder struct {
Commander Commander
active *Transaction
}
// Register creates a new transaction based on given modifier.
func (builder *TransactionBuilder) Register(modifier ...TransactionModifier) error {
previous := builder.active
current := &Transaction{}
builder.active = current
for _, mod := range modifier {
err := mod(current)
if err != nil {
return err
}
}
builder.active = previous
// invert reverse task for easier iteration
for left, right := 0, len(current.Reverse)-1; left < right; left, right = left+1, right-1 {
current.Reverse[left], current.Reverse[right] = current.Reverse[right], current.Reverse[left]
}
builder.Queue(current)
return nil
}
// Queue implements the Commander interface to add given command to a currently built transaction.
// If there is no outer transaction being built, then the command is immediately forwarded to the commander.
// Avoid building a loop between TransactionBuilder instances, as this will result in a stack overflow.
func (builder *TransactionBuilder) Queue(command Command) {
if builder.active == nil {
builder.Commander.Queue(command)
return
}
builder.active.Forward = append(builder.active.Forward, command.Do)
builder.active.Reverse = append(builder.active.Reverse, command.Undo)
}
// Named allows to name the current level of transaction.
func Named(name string) TransactionModifier {
return func(txn *Transaction) error {
txn.Name = name
return nil
}
}
// Forward creates a modifier for a task that is called when doing a command.
// If a nested command is added to the transaction after the forward function, then
// the nested task is performed after the given task.
// If a nested command is added to the transaction before the forward function, then
// the nested task is performed before the given task.
func Forward(task Task) TransactionModifier {
return func(txn *Transaction) error {
txn.Forward = append(txn.Forward, task)
return nil
}
}
// Reverse creates a modifier for a task that is called when undoing a command.
// If a nested command is added to the transaction after the reverse function, then
// the nested task is performed before the given task.
// If a nested command is added to the transaction before the reverse function, then
// the nested task is performed after the given task.
func Reverse(task Task) TransactionModifier {
return func(txn *Transaction) error {
txn.Reverse = append(txn.Reverse, task)
return nil
}
}
// Nested adds a further level to the currently registered transaction.
// Depending on the order of the modifier in the register, the corresponding tasks will be called
// in sequence. See other modifier.
func Nested(nested func() error) TransactionModifier {
return func(txn *Transaction) error {
return nested()
}
}