Skip to content

Instantly share code, notes, and snippets.

@progrium
Last active May 18, 2021
Embed
What would you like to do?
package main
import (
"fmt"
"github.com/.../fds"
"github.com/.../fds/dict"
"github.com/.../fds/list"
"github.com/.../fds/tree"
)
// Faceted Data Structure is a dynamic data structure construction kit
// based on Entities that can have Facets, which can just contain data,
// or also implement an interface that allow the Entity to be used in new
// kinds of operations. No reflection is involved.
// Here we have an entity. Alone it has an ID string. This is typically
// some kind of GUID. If you need a numeric ID you can use the pointer
// ID. You can also leave the string ID empty.
func main1() {
e := fds.NewEntity("abc")
fmt.Println(e.ID(), fds.PtrID(e))
// Output: abc 824634322744
}
// Here we make an entity with a builtin facet for storing a value,
// which we can get with fds.Value(), returning an interface{}
func main2() {
e := fds.NewEntity("", fds.ValueFacet{Value: 321})
fmt.Println(fds.Value(e))
// Output: 321
}
// We start to introduce structure with a ListFacet entity. From
// this facet we can start appending several entities with values,
// though what facets they have is not important to the ListFacet.
// It's important to note the ListFacet implements the List interface
// which allows most slice operations as well as getting a slice.
func main3() {
lst := list.Facet(fds.NewEntity("", &list.ListFacet{}))
e1 := fds.NewEntity("", fds.ValueFacet{Value: 1})
lst.Append(e1)
e2 := fds.NewEntity("", fds.ValueFacet{Value: 2})
lst.Append(e2)
fmt.Println(lst.Len())
// Output: 2
for _, e := range lst.Slice() {
fmt.Println(fds.Value(e))
}
// Output:
// 1
// 2
}
// Although we could use the GUID, we also have a KeyFacet. Combined
// with a ListFacet, we get a sort of ordered dictionary. However, it
// still implements the List interface, so we can use a Move function
// to reorder the list. Then we see a dict.Value() that works based on
// the convention of a List of Key-Value entities like this.
func main4() {
e := fds.NewEntity("", &list.ListFacet{})
lst := list.Facet(e)
lst.Append(fds.NewEntity("", fds.KeyFacet{Key: "foo"}, fds.ValueFacet{Value: 123}))
lst.Append(fds.NewEntity("", fds.KeyFacet{Key: "bar"}, fds.ValueFacet{Value: 456}))
lst.Append(fds.NewEntity("", fds.KeyFacet{Key: "baz"}, fds.ValueFacet{Value: 789}))
list.Move(lst, 2, 0)
for _, e := range lst.Slice() {
fmt.Println(fds.Key(e), ":", fds.Value(e))
}
// Output:
// baz : 789
// foo : 123
// bar : 456
fmt.Println(dict.Value(e, "foo"))
// Output: 123
}
// Now we can get more complicated and build a tree. Given a
// tree.NodeFacet, entities now have children. Children is just
// a List, which can still be operated on with list.Move, even
// though appending to children will add a NodeFacet. This means
// after appending to children we can move them in the tree by
// setting their parent. Again, since the children have keys,
// we can also treat them like a dictionary with dict.Value.
func main5() {
root := tree.FacetNode(fds.NewEntity("", &tree.NodeFacet{}))
e1 := fds.NewEntity("", fds.KeyFacet{Key: "foo"}, fds.ValueFacet{Value: 123})
root.Children().Append(e1)
e2 := fds.NewEntity("", fds.KeyFacet{Key: "bar"}, fds.ValueFacet{Value: 456})
root.Children().Append(e2)
e3 := fds.NewEntity("", fds.KeyFacet{Key: "baz"}, fds.ValueFacet{Value: 789})
root.Children().Append(e3)
list.Move(root.Children(), 2, 0)
baz := tree.FacetNode(root.Children().Index(0))
bar := tree.FacetNode(root.Children().Index(2))
bar.SetParent(baz)
for _, e := range root.Children().Slice() {
fmt.Println(fds.Key(e), ":", fds.Value(e))
children := tree.FacetNode(e).Children()
if children.Len() > 0 {
for _, e := range children.Slice() {
fmt.Println(" ", fds.Key(e), ":", fds.Value(e))
}
}
}
// Output:
// baz : 789
// bar : 456
// foo : 123
fmt.Println(dict.Value(root.Children(), "baz"))
// Output: 789
}
// Lastly we can make our own facets. They just need to implement
// the Facet interface and return a mask ID. However, with generics
// this is simplified as we can look them up with type params.
// This would also eliminate the need for the convention of facet
// accessor functions like FacetCustom.
var CustomFacetID = fds.NextFacetID()
type CustomFacet struct {
Str string
Num int
}
func (f *CustomFacet) Mask() fds.FacetMask {
return CustomFacetID
}
func FacetCustom(e *fds.Entity) *CustomFacet {
if l := e.Get(maskCustomFacet); l != nil {
return l.(*CustomFacet)
}
return nil
}
// But now we can do all of the above, but our entities can include
// our custom facet's data, as well as any operations they open up
// if our facet implemented any interfaces.
func main6() {
e := fds.NewEntity("", &list.ListFacet{})
lst := list.Facet(e)
lst.Append(fds.NewEntity("", fds.KeyFacet{Key: "foo"}, &CustomFacet{Str: "Foo", Num: 123}))
lst.Append(fds.NewEntity("", fds.KeyFacet{Key: "bar"}, &CustomFacet{Str: "Bar", Num: 456}))
lst.Append(fds.NewEntity("", fds.KeyFacet{Key: "baz"}, &CustomFacet{Str: "Baz", Num: 789}))
fmt.Println(FacetCustom(dict.Get(e, "bar")))
// Output: &{Bar 456}
}
@c-nv-s
Copy link

c-nv-s commented May 8, 2021

Absolutely love your work!
this project will be a nice complement to https://github.com/emirpasic/gods

@mbrevoort
Copy link

mbrevoort commented May 8, 2021

It would have helped a lot if I was already familiar with ECS. After 20 minutes of re search I get it now!

I would love to see a contrast based on a relatable use case between (a) brute force mess of an implementation and (b) this faceted dynamic data structure approach.

@mbrevoort
Copy link

mbrevoort commented May 8, 2021

In these examples you always know which entities have which facets, like on line 167. Would you expect a system that knows how to operate on FacetCustom to maintain a list of those entities?

What would happen below?

lst.Append(fds.NewEntity("", fds.KeyFacet{Key: "fiz"}, &ValueFacet{Value: 777}))
fmt.Println(FacetCustom(dict.Get(e, "fiz")))

@progrium
Copy link
Author

progrium commented May 8, 2021

In these examples you always know which entities have which facets, like on line 167. Would you expect a system that knows how to operate on FacetCustom to maintain a list of those entities?

Up to you. Smaller systems can just check, larger systems can be designed more like ECS where code only works with facets.

What would happen below?

lst.Append(fds.NewEntity("", fds.KeyFacet{Key: "fiz"}, &ValueFacet{Value: 777}))
fmt.Println(FacetCustom(dict.Get(e, "fiz")))

You'd get a nil. I'm always torn between returning just the value or nil, or also returning a bool like a type assertion. But the latter starts to really make things verbose. When generics drop, I could see two functions for either situation, neither of which you'd have to write for new facets.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment