goki / ki

Development of Goki has moved to Cogent Core. For the latest stable version of ki v1, import version 1.1.17 and see the v1 branch.
https://GoKi.dev
BSD 3-Clause "New" or "Revised" License
89 stars 7 forks source link

Use Enumer and BitEnumer interfaces instead of enums type registry? #13

Closed rcoreilly closed 1 year ago

rcoreilly commented 1 year ago

Basic Enumer would just require FromString method -- very easy. Add a kit.SetEnumFromString that just checks for the interface and calls it directly. Much simpler than what is there now.

BitEnumer FromStringBits would call a kit.BitFlagsFromString method -- it just parses the | and uses FromString.

The main missing part here would be the alt strings -- need to see exactly what this is, but it may just be lower case versions?

rcoreilly commented 1 year ago

plan:

type Enumer interface {
   fmt.Stringer
   FromString(s string) error
   Values() []int64
   Strings() []string
}

type BitEnumer interface {
    Enumer
    HasBitFlag(f int64) bool  // note: generic version of flag methods always taking int64 -- for use in generic gui code etc
    SetBitFlag(f int64)
}
// MyEnum is an enum
type MyEnum int //enumer:enum

// MyBitEnum is a bitflag enum
type MyBitEnum int //enumer:bitflag
func (e *MyBitEnum) Has(f MyBitEnum) bool {
   //  bitflag.HasFlagAtomic((*int64)(e), int64(f)) // key point: generator knows to use 64 vs 32 bit versions here
   // also, just use the actual code instead of requiring bitflag:
    return (atomic.LoadInt64((*int64)(e) & int64(f)) != 0  // note: atomic not much more expensive and almost always preferred
}

// etc for set flag

Extending Enum types

Add support for enums and bitflags that extend existing enums -- does range checking, etc.

in current ki-based code:

var TypeNodeFlags = kit.Enums.AddEnumExt(ki.KiT_Flags, NodeFlagsN, kit.BitFlag, nil)

const (
    // NoLayout means that this node does not participate in the layout
    // process (Size, Layout, Move) -- set by e.g., SVG nodes
    NoLayout NodeFlags = NodeFlags(ki.FlagsN) + iota

new way -- generator reads the base type to know that it extends

type NewEnum MyEnum //enumer:enum

This does the following:

// in package ki:
type Flags int64 //enumer:bitflag
type Node struct {
   Flag Flags // this must be of type Flags so the gui knows how to treat it.
}

// in package ki:
type ButtonFlags ki.Flags //enumer:bitflag

// access inherited flag in random code
var f ki.Flags // some random enum var
if f.Has(ki.Updating) // this works automatically

// using extended flag, you have to use an explicit cast to access extended enum values
if ButtonFlags(f).Has(Hovered) // this only works for actual ButtonFlags..

// the other way:
var bf ButtonFlags
if bf.Has(Hovered) // no problem

if ki.Flags(bf).Has(ki.Updating) // same thing

// generic global method version:

// in enumer, generic method working with any BitEnumer type:
func Has[B BitEnumer, F constraints.Integer](b B, f F) bool {
    return b.HasBitFlag(int64(f))
}

// for the above cases of f, bf -- bf or f works the same
if enumer.Has(f, ki.Updating)
if enumer.Has(f, Hovered)

Struct Field Flags

Option 1: create Is, Set methods

Benefit: embedded types will automatically inherit these methods Cost: lots of methods!

// Button is a ...
type Button struct {  //enumer:structflag=ButtonFlags field=Flag
    ...
}

// generates methods for only the new flags in ButtonFlags:
func (b *Button) IsHovered() bool 
func (b *Button) SetHovered(b bool)
...

(flag can default to Flag but I think it is better not to have magic default behavior)

Option 2: create HasFlag, SetFlag methods

// generated code:
func (b *Button) HasFlag(f ButtonFlag) bool
func (b *Button) SetFlag(f ButtonFlag)

// access parent version using embedded type:
b.HasFlag(Hovered) // easy access of button flags
b.Node.HasFlag(ki.Updating) // requires knowing which level has what flags..

Option 3: hand-written generic global function in ki.go

This is not really relevant to enumer because it is just hand coded and would require a similar hand-coded case for anyone else to impl.

// in ki.go:
func HasFlag[T constraints.Integer](k Ki, f T) bool {
    return k.HasFlag(int64(f)) // note: can then use proper int64 instead of 
}

// use case:
ki.HasFlag(b, Hovered)

Option 4: enumer Has function as already described above

enumer.Has(b.Flag, Hovered)

compare all the options:

// button Hovered case:
b.Flag.Has(ki.Flags(Hovered)) // no!
enumer.Has(b.Flag, Hovered) // free -- going to need anyway
ki.HasFlag(b, Hovered)  // also easy
b.HasFlag(Hovered)   // no!
b.IsHovered()

// ki.Updating case:
b.Flag.Has(ki.Updating) // no!
enumer.Has(b.Flag, ki.Updating)  // free
ki.HasFlag(b, ki.Updating) // easy
b.Node.HasFlag(ki.Updating)  // no!
b.IsUpdating()

So obviously the question is: Is?

Optional type registry

optional type registry functionality, needed for loading arbitrary enum types and values from files. This adds the kit.AddEnum code in generate.

rcoreilly commented 1 year ago

enums package all good!