From 690b9ca33971c0fc6e2f4dbafeb66394a22b9d5a Mon Sep 17 00:00:00 2001 From: bouncepaw Date: Tue, 6 Apr 2021 23:47:35 +0500 Subject: [PATCH] Rewrite the tree using the new iteration object WIP --- hyphae/iteration.go | 52 +++++++++++ tree/tree.go | 217 +++++++++++++++++++++++++------------------- 2 files changed, 174 insertions(+), 95 deletions(-) create mode 100644 hyphae/iteration.go diff --git a/hyphae/iteration.go b/hyphae/iteration.go new file mode 100644 index 0000000..a33e9ac --- /dev/null +++ b/hyphae/iteration.go @@ -0,0 +1,52 @@ +package hyphae + +import ( + "sync" +) + +// Iteration represents an iteration over all hyphae in the storage. You may use it instead of directly iterating using hyphae.YieldExistingHyphae when you want to do n checks at once instead of iterating n times. +type Iteration struct { + sync.Mutex + iterator func() chan *Hypha + checks []func(h *Hypha) CheckResult +} + +func NewIteration() *Iteration { + return &Iteration{ + iterator: YieldExistingHyphae, + checks: make([]func(h *Hypha) CheckResult, 0), + } +} + +// AddCheck adds the check to the iteration. It is concurrent-safe. +func (i7n *Iteration) AddCheck(check func(h *Hypha) CheckResult) { + i7n.Lock() + i7n.checks = append(i7n.checks, check) + i7n.Unlock() +} + +func (i7n *Iteration) removeCheck(i int) { + i7n.checks[i] = i7n.checks[len(i7n.checks)-1] + i7n.checks = i7n.checks[:len(i7n.checks)-1] +} + +// Ignite does the iteration by walking over all hyphae yielded by the iterator used and calling all checks on the hypha. Ignited iterations are not concurrent-safe. +func (i7n *Iteration) Ignite() { + for h := range i7n.iterator() { + for i, check := range i7n.checks { + if res := check(h); res == CheckForgetMe { + i7n.removeCheck(i) + } + } + } +} + +// CheckResult is a result of an iteration check. +type CheckResult int + +const ( + // CheckContinue is returned when the check wants to be used next time too. + CheckContinue CheckResult = iota + // CheckForgetMe is returned when the check wants to be forgotten and not used anymore. + CheckForgetMe +) diff --git a/tree/tree.go b/tree/tree.go index dc3cf38..d1fbf48 100644 --- a/tree/tree.go +++ b/tree/tree.go @@ -5,25 +5,131 @@ import ( "path" "sort" "strings" + "sync" "github.com/bouncepaw/mycorrhiza/hyphae" "github.com/bouncepaw/mycorrhiza/util" ) -type sibling struct { - name string - hasChildren bool +func findSiblingsAndDescendants(hyphaName string) ([]*sibling, map[string]bool) { + var ( + siblings = make([]*sibling, 0) + siblingCheck = func(h *hyphae.Hypha) hyphae.CheckResult { + if path.Dir(hyphaName) == path.Dir(h.Name) { + siblings = append(siblings, &sibling{h.Name, 0, 0}) + } + return hyphae.CheckContinue + } + + descendantsPool = make(map[string]bool, 0) + descendantCheck = func(h *hyphae.Hypha) hyphae.CheckResult { + if strings.HasPrefix(h.Name, hyphaName+"/") { + descendantsPool[h.Name] = true + } + return hyphae.CheckContinue + } + + i7n = hyphae.NewIteration() + ) + i7n.AddCheck(siblingCheck) + i7n.AddCheck(descendantCheck) + i7n.Ignite() + sort.Slice(siblings, func(i, j int) bool { + return siblings[i].name < siblings[j].name + }) + return siblings, descendantsPool } -func (s *sibling) checkThisChild(hyphaName string) { - if !s.hasChildren && path.Dir(hyphaName) == s.name { - s.hasChildren = true +func countSubhyphae(siblings []*sibling) { + var ( + subhyphaCheck = func(h *hyphae.Hypha) hyphae.CheckResult { + for _, s := range siblings { + if path.Dir(h.Name) == s.name { + s.directSubhyphaeCount++ + return hyphae.CheckContinue + } else if strings.HasPrefix(h.Name, s.name+"/") { + s.indirectSubhyphaeCount++ + return hyphae.CheckContinue + } + } + return hyphae.CheckContinue + } + i7n = hyphae.NewIteration() + ) + i7n.AddCheck(subhyphaCheck) + i7n.Ignite() +} + +// Tree generates a tree for `hyphaName` as html and returns next and previous hyphae if any. +func Tree(hyphaName string) (siblingsHTML, childrenHTML, prev, next string) { + children := make([]child, 0) + I := 0 + // The tree is generated in two iterations of hyphae storage: + // 1. Find all siblings (sorted) and descendants' names + // 2. Count how many subhyphae siblings have + // + // We also have to figure out what is going on with the descendants: who is a child of whom. We do that in parallel with (2) because we can. + // One of the siblings is the hypha with name `hyphaName` + siblings, descendantsPool := findSiblingsAndDescendants(hyphaName) + + wg := sync.WaitGroup{} + wg.Add(2) + go func() { + countSubhyphae(siblings) + wg.Done() + }() + go func() { + children = figureOutChildren(hyphaName, descendantsPool).children + wg.Done() + }() + wg.Wait() + + for i, s := range siblings { + if s.name == hyphaName { + I = i + siblingsHTML += fmt.Sprintf(`
  • %s
  • `, util.BeautifulName(path.Base(hyphaName))) + } else { + siblingsHTML += s.asHTML(hyphaName) + } } + if I != 0 { + prev = siblings[I-1].name + } + if I != len(siblings)-1 { + next = siblings[I+1].name + } + return fmt.Sprintf(``, siblingsHTML), subhyphaeMatrix(children), prev, next } -func (s *sibling) asHTML() string { +type child struct { + name string + children []child +} + +func figureOutChildren(hyphaName string, subhyphaePool map[string]bool) child { + var ( + nestLevel = strings.Count(hyphaName, "/") + adopted = make([]child, 0) + ) + for subhyphaName, _ := range subhyphaePool { + subnestLevel := strings.Count(subhyphaName, "/") + if subnestLevel-1 == nestLevel && path.Dir(subhyphaName) == hyphaName { + delete(subhyphaePool, subhyphaName) + adopted = append(adopted, figureOutChildren(subhyphaName, subhyphaePool)) + } + } + return child{hyphaName, adopted} +} + +type sibling struct { + name string + directSubhyphaeCount int + indirectSubhyphaeCount int +} + +func (s *sibling) asHTML(hyphaName string) string { class := "navitree__entry navitree__sibling" - if s.hasChildren { + if s.directSubhyphaeCount > 0 { class += " navitree__sibling_fertile navitree__entry_fertile" } else { class += " navitree__sibling_infertile navitree__entry_infertile" @@ -36,54 +142,21 @@ func (s *sibling) asHTML() string { ) } -type mainFamilyMember struct { - name string - children []*mainFamilyMember -} - -func (m *mainFamilyMember) checkThisChild(hyphaName string) (adopted bool) { - if path.Dir(hyphaName) == m.name { - m.children = append(m.children, &mainFamilyMember{ - name: hyphaName, - children: make([]*mainFamilyMember, 0), - }) - return true +func (c *child) asHTML() string { + if len(c.children) == 0 { + return fmt.Sprintf(`
  • %s
  • `, c.name, util.BeautifulName(path.Base(c.name))) } - return false -} - -func (m *mainFamilyMember) asHTML() string { - if len(m.children) == 0 { - return fmt.Sprintf(`
  • %s
  • `, m.name, util.BeautifulName(path.Base(m.name))) - } - sort.Slice(m.children, func(i, j int) bool { - return m.children[i].name < m.children[j].name + sort.Slice(c.children, func(i, j int) bool { + return c.children[i].name < c.children[j].name }) - html := fmt.Sprintf(`
  • %s