Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 61 additions & 41 deletions server/session/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ type translation interface {

// sendAvailableCommands sends all available commands of the server. Once sent, they will be visible in the
// /help list and will be auto-completed.
func (s *Session) sendAvailableCommands(co Controllable) map[string]map[int]cmd.Runnable {
func (s *Session) sendAvailableCommands(co Controllable, softEnums map[string]struct{}) map[string]map[int]cmd.Runnable {
commands := cmd.Commands()
m := make(map[string]map[int]cmd.Runnable, len(commands))

Expand Down Expand Up @@ -84,40 +84,39 @@ func (s *Session) sendAvailableCommands(co Controllable) map[string]map[int]cmd.
for i, params := range params {
for _, paramInfo := range params {
t, enum := valueToParamType(paramInfo, co)
t |= protocol.CommandArgValid
suffix := paramInfo.Suffix

opt := byte(0)
if _, ok := paramInfo.Value.(bool); ok {
opt |= protocol.ParamOptionCollapseEnum
}
if len(enum.Options) > 0 || enum.Type != "" {
if !enum.Dynamic {
index, ok := enumIndices[enum.Type]
if !ok {
index = uint32(len(enums))
enumIndices[enum.Type] = index
enums = append(enums, enum)
}
t |= protocol.CommandArgEnum | index
} else {
index, ok := dynamicEnumIndices[enum.Type]
if !ok {
index = uint32(len(dynamicEnums))
dynamicEnumIndices[enum.Type] = index
dynamicEnums = append(dynamicEnums, enum)
}
t |= protocol.CommandArgSoftEnum | index
}
}
if suffix != "" {
index, ok := suffixIndices[suffix]
if !ok {
index = uint32(len(pk.Suffixes))
suffixIndices[suffix] = index
pk.Suffixes = append(pk.Suffixes, suffix)
}
t |= protocol.CommandArgSuffixed | index
t = protocol.CommandArgSuffixed | index
} else {
t |= protocol.CommandArgValid
if len(enum.Options) > 0 || enum.Type != "" {
_, dynamic := softEnums[enum.Type]
if !dynamic {
index, ok := enumIndices[enum.Type]
if !ok {
index = uint32(len(enums))
enumIndices[enum.Type] = index
enums = append(enums, enum)
}
t |= protocol.CommandArgEnum | index
} else {
index, ok := dynamicEnumIndices[enum.Type]
if !ok {
index = uint32(len(dynamicEnums))
dynamicEnumIndices[enum.Type] = index
dynamicEnums = append(dynamicEnums, enum)
}
t |= protocol.CommandArgSoftEnum | index
}
}
}
overloads[i].Parameters = append(overloads[i].Parameters, protocol.CommandParameter{
Name: paramInfo.Name,
Expand Down Expand Up @@ -164,7 +163,6 @@ func (s *Session) sendAvailableCommands(co Controllable) map[string]map[int]cmd.
type commandEnum struct {
Type string
Options []string
Dynamic bool
}

// valueToParamType finds the command argument type of the value passed and returns it, in addition to creating
Expand All @@ -183,8 +181,8 @@ func valueToParamType(i cmd.ParamInfo, source cmd.Source) (t uint32, enum comman
return protocol.CommandArgTypeTarget, enum
case bool:
return 0, commandEnum{
Type: "bool",
Options: []string{"true", "1", "false", "0"},
Type: "Boolean",
Options: []string{"true", "false"},
}
case mgl64.Vec3:
return protocol.CommandArgTypePosition, enum
Expand All @@ -198,7 +196,6 @@ func valueToParamType(i cmd.ParamInfo, source cmd.Source) (t uint32, enum comman
return 0, commandEnum{
Type: enum.Type(),
Options: enum.Options(source),
Dynamic: true,
}
}
return protocol.CommandArgTypeValue, enum
Expand All @@ -207,7 +204,11 @@ func valueToParamType(i cmd.ParamInfo, source cmd.Source) (t uint32, enum comman
// resendCommands resends all commands that a Session has access to if the map of runnable commands passed does not
// match with the commands that the Session is currently allowed to execute.
// True is returned if the commands were resent.
func (s *Session) resendCommands(before map[string]map[int]cmd.Runnable, co Controllable) (map[string]map[int]cmd.Runnable, bool) {
func (s *Session) resendCommands(
before map[string]map[int]cmd.Runnable,
co Controllable,
softEnums map[string]struct{},
) (map[string]map[int]cmd.Runnable, bool) {
commands := cmd.Commands()
m := make(map[string]map[int]cmd.Runnable, len(commands))

Expand All @@ -219,21 +220,21 @@ func (s *Session) resendCommands(before map[string]map[int]cmd.Runnable, co Cont
}
}
if len(before) != len(m) {
return s.sendAvailableCommands(co), true
return s.sendAvailableCommands(co, softEnums), true
}
// First check for commands that were newly added.
for name, r := range m {
for k := range r {
if _, ok := before[name][k]; !ok {
return s.sendAvailableCommands(co), true
return s.sendAvailableCommands(co, softEnums), true
}
}
}
// Then check for commands that a player could execute before, but no longer can.
for name, r := range before {
for k := range r {
if _, ok := m[name][k]; !ok {
return s.sendAvailableCommands(co), true
return s.sendAvailableCommands(co, softEnums), true
}
}
}
Expand All @@ -259,22 +260,41 @@ func (s *Session) enums(co Controllable) (map[string]cmd.Enum, map[string][]stri
}

// resendEnums checks the options of the enums passed against the values that were previously recorded. If they do not
// match, the enum is resent to the client and the values are updated in the before map.
func (s *Session) resendEnums(enums map[string]cmd.Enum, before map[string][]string, c Controllable) {
// match, and the enum is in softEnums, the enum is resent via UpdateSoftEnum. If the enum is not yet in softEnums,
// it is added and the full AvailableCommands packet is resent.
func (s *Session) resendEnums(
enums map[string]cmd.Enum,
before map[string][]string,
softEnums map[string]struct{},
r map[string]map[int]cmd.Runnable,
c Controllable,
) map[string]map[int]cmd.Runnable {
for name, enum := range enums {
valuesBefore := before[name]
values := enum.Options(c)
before[name] = values

changed := false
if len(valuesBefore) != len(values) {
s.writePacket(&packet.UpdateSoftEnum{EnumType: name, Options: values, ActionType: packet.SoftEnumActionSet})
changed = true
} else {
for k, v := range values {
if valuesBefore[k] != v {
changed = true
break
}
}
}
if !changed {
continue
}
for k, v := range values {
if valuesBefore[k] != v {
s.writePacket(&packet.UpdateSoftEnum{EnumType: name, Options: values, ActionType: packet.SoftEnumActionSet})
break
}

if _, dynamic := softEnums[name]; dynamic {
s.writePacket(&packet.UpdateSoftEnum{EnumType: name, Options: values, ActionType: packet.SoftEnumActionSet})
} else {
softEnums[name] = struct{}{}
r = s.sendAvailableCommands(c, softEnums)
}
}
return r
}
7 changes: 4 additions & 3 deletions server/session/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -370,13 +370,14 @@ func (s *Session) background() {
r map[string]map[int]cmd.Runnable
enums map[string]cmd.Enum
enumValues map[string][]string
softEnums = make(map[string]struct{})
ok bool
i int
)

s.ent.ExecWorld(func(tx *world.Tx, e world.Entity) {
co := e.(Controllable)
r = s.sendAvailableCommands(co)
r = s.sendAvailableCommands(co, softEnums)
enums, enumValues = s.enums(co)
})

Expand All @@ -391,11 +392,11 @@ func (s *Session) background() {
if i++; i%20 == 0 {
// Enum resending happens relatively often and frequent updates are more important than with full
// command changes. Those are generally only related to permission changes, which doesn't happen often.
s.resendEnums(enums, enumValues, c)
r = s.resendEnums(enums, enumValues, softEnums, r, c)
}
if i%100 == 0 {
// Try to resend commands only every 5 seconds.
if r, ok = s.resendCommands(r, c); ok {
if r, ok = s.resendCommands(r, c, softEnums); ok {
enums, enumValues = s.enums(c)
}
}
Expand Down