package membership // Status identifies one lifecycle state of a Game Lobby membership record. type Status string const ( // StatusActive reports that the member is a full participant and may // send commands through Game Master. StatusActive Status = "active" // StatusRemoved reports that the member was removed post-start. The // engine slot is deactivated; the race name reservation is preserved // until the game finishes. StatusRemoved Status = "removed" // StatusBlocked reports that the member is blocked at the platform // level. The engine slot is retained; commands are blocked. StatusBlocked Status = "blocked" ) // IsKnown reports whether status belongs to the frozen membership status // vocabulary. func (status Status) IsKnown() bool { switch status { case StatusActive, StatusRemoved, StatusBlocked: return true default: return false } } // IsTerminal reports whether status can no longer accept lifecycle // transitions. func (status Status) IsTerminal() bool { switch status { case StatusRemoved, StatusBlocked: return true default: return false } } // transitionKey stores one `(from, to)` pair in the allowed-transitions // table. type transitionKey struct { from Status to Status } // allowedTransitions stores the set of permitted `(from, to)` status pairs. // It mirrors the state machine frozen in lobby/README.md Membership Model // section. var allowedTransitions = map[transitionKey]struct{}{ {StatusActive, StatusRemoved}: {}, {StatusActive, StatusBlocked}: {}, } // AllowedTransitions returns a copy of the `(from, to)` allowed-transitions // table used by Transition. The returned map is safe to mutate. func AllowedTransitions() map[Status][]Status { result := make(map[Status][]Status) for key := range allowedTransitions { result[key.from] = append(result[key.from], key.to) } return result } // Transition reports whether from may transition to next. The function // returns nil when the pair is permitted, and an *InvalidTransitionError // wrapping ErrInvalidTransition otherwise. It does not touch any store and // is safe to call from any layer. func Transition(from Status, next Status) error { if !from.IsKnown() || !next.IsKnown() { return &InvalidTransitionError{From: from, To: next} } if _, ok := allowedTransitions[transitionKey{from: from, to: next}]; !ok { return &InvalidTransitionError{From: from, To: next} } return nil }