Quantcast
Viewing all articles
Browse latest Browse all 1449

VB6 nestable UDT-based Node-Class with fast teardown

Not much to it, just a Demo for an UDT-based cNode-Class,
which can be extended (by adding Fields to the Class-internal UDT).

This Node-Class is "self-contained" (no extra-modules are needed).

Performance is quite good... adding 1,001,000 Nodes total as 1000ChildNodes on 1000ChildNodes takes:
- about 0.33sec in the IDE
- about 0.22sec native compiled (all Options)

cNode will hand out "dynamically created wrapper instances of itself -
(using internal Indexes which point into the Root-UDT-Array)", on all cNode-returning Properties, which are:
- Root() As cNode
- Parent() As cNode
- NodeById(ByVal ID As Long) As cNode
- Child(ByVal IdxZeroBased As Long) As cNode
- FirstChild() As cNode
- NextSibling() As cNode
- PrevSibling() As cNode
- LastChild() As cNode


cNode
Code:

Option Explicit

Private Declare Sub AssignArrTo Lib "kernel32" Alias "RtlMoveMemory" (pDst() As Any, pSrc() As Any, Optional ByVal CB& = 4)

Private Type tNode
  PIdx As Long
  ChCount As Long
  ChIdxs() As Long
  'define UserData-NodeProps from here onwards...
  Text As String
  '... a.s.o. (see the Prop-Mapping-Section at the end of this Class)
End Type

'we need only 3 Private Vars to present a Node-instance
Private mIdx As Long, mNodes() As tNode, mInternalInstance As Boolean

Private Sub Class_Initialize()
  ReDim mNodes(0)
End Sub
Private Sub Class_Terminate() 'cleanup the fake arr-reference...
  Dim aNull(): If mInternalInstance Then AssignArrTo mNodes, aNull '...but only when created internally here
End Sub

Public Sub AddNode(Text As String) 'for best add-performance, include all the (UDT-)Prop-Values as Params here
  Dim UB As Long, CC As Long
      UB = UBound(mNodes)
 
  CC = -mNodes(0).PIdx + 1: mNodes(0).PIdx = -CC '<- mNodes(0).PIdx holds the negative, total ChildNode-Count
  If CC >= UB Then ReDim Preserve mNodes(32 + CC * 1.6)
 
 
  With mNodes(CC) 'set the direct UDT-entries of our new ChildNode
    .PIdx = mIdx  '... starting with the ParentIndex (which is the Index of *this* (Parent)Node-instance)
    .Text = Text
    '... a.s.o. for more UDT-Values (see the UDT-def at the top of this Class)
  End With
 
  With mNodes(mIdx) 'also put the right Index-entry into the Child-Array of the UDT for this (Parent)Node-Instance
    If .ChCount = 0 Then ReDim .ChIdxs(4)
    If .ChCount >= UBound(.ChIdxs) Then ReDim Preserve .ChIdxs(.ChCount * 1.6)
    .ChIdxs(.ChCount) = CC 'set the Index of the new Child (CC is equivalent with that)
    .ChCount = .ChCount + 1
  End With
End Sub
 
Friend Sub Init(ByVal Idx As Long, Nodes() As tNode) 'do not call this method from the outside
  mIdx = Idx: mInternalInstance = True 'set the Idx + flag this instance as "internally created"
  Erase mNodes: AssignArrTo mNodes, Nodes 'make a "fake" Array-copy
End Sub

Public Property Get ID() As Long 'to provide a unique Identifier within the Tree for this Node
  ID = mIdx 'on the outside, this is only useful to compare Nodes for identity
End Property

Public Property Get TotalNodeCount() As Long
  TotalNodeCount = -mNodes(0).PIdx
End Property

Public Property Get Level() As Long 'determines the "Hierarchy-Depth" of the current Node
  Dim i As Long: i = mIdx
  Do While i: i = mNodes(i).PIdx: Level = Level + 1: Loop
End Property

Public Property Get Root() As cNode
  Set Root = New cNode: Root.Init 0, mNodes
End Property
Public Property Get Parent() As cNode
  If mIdx Then Set Parent = New cNode: Parent.Init mNodes(mIdx).PIdx, mNodes
End Property
Public Property Get NodeById(ByVal ID As Long) As cNode
  Set NodeById = New cNode: NodeById.Init ID, mNodes
End Property
Public Property Get Child(ByVal IdxZeroBased As Long) As cNode
  Set Child = New cNode: Child.Init mNodes(mIdx).ChIdxs(IdxZeroBased), mNodes
End Property
Public Property Get ChildCount() As Long
  ChildCount = mNodes(mIdx).ChCount
End Property

Public Property Get FirstChild() As cNode
  If mNodes(mIdx).ChCount = 0 Then Exit Property 'no first Child available here (return Nothing)
  Set FirstChild = New cNode: FirstChild.Init mNodes(mIdx).ChIdxs(0), mNodes
End Property
Public Property Get NextSibling() As cNode
  If mIdx = 0 Then Exit Property 'the Root-Node has no siblings
  With mNodes(mNodes(mIdx).PIdx)
    If .ChCount <= 1 Then Exit Property 'with a ChildCount <=1 there's no next Sibling
    If .ChIdxs(.ChCount - 1) = mIdx Then Exit Property 'the last Child has no next Sibling
    Dim i As Long
    i = mIdx - .ChIdxs(0) 'simple optimization-attempt (avoiding the loop, when the second-next line checks out true)
    If i < 0 Then i = 0 Else If i > .ChCount - 2 Then i = .ChCount - 2   
    If .ChIdxs(i) = mIdx Then Set NextSibling = New cNode: NextSibling.Init .ChIdxs(i + 1), mNodes: Exit Property
    For i = 0 To .ChCount - 2
      If .ChIdxs(i) = mIdx Then Set NextSibling = New cNode: NextSibling.Init .ChIdxs(i + 1), mNodes: Exit For
    Next
  End With
End Property
Public Property Get PrevSibling() As cNode
  If mIdx = 0 Then Exit Property 'the Root-Node has no siblings
  With mNodes(mNodes(mIdx).PIdx)
    If .ChCount <= 1 Then Exit Property 'with a ChildCount <=1 there's no previous Sibling
    If .ChIdxs(0) = mIdx Then Exit Property 'the first Child has no previous Sibling
    Dim i As Long
    i = mIdx - .ChIdxs(0) 'simple optimization-attempt (avoiding the loop, when the second-next line checks out true)
    If i < 1 Then i = 1 Else If i > .ChCount - 1 Then i = .ChCount - 1
    If .ChIdxs(i) = mIdx Then Set PrevSibling = New cNode: PrevSibling.Init .ChIdxs(i - 1), mNodes: Exit Property
    For i = 1 To .ChCount - 1
      If .ChIdxs(i) = mIdx Then Set PrevSibling = New cNode: PrevSibling.Init .ChIdxs(i - 1), mNodes: Exit For
    Next
  End With
End Property
Public Property Get LastChild() As cNode
  If mNodes(mIdx).ChCount = 0 Then Exit Property 'no last Child available here (return Nothing)
  Set LastChild = New cNode: LastChild.Init mNodes(mIdx).ChIdxs(mNodes(mIdx).ChCount - 1), mNodes
End Property

'Ok, finally the mapping of (non-navigation-related) UDT-Props to and from the outside
Public Property Get Text() As String
  Text = mNodes(mIdx).Text
End Property
Public Property Let Text(RHS As String)
  mNodes(mIdx).Text = RHS
End Property

Into a Test-Form:
Code:

Option Explicit

Private Root As cNode

Private Sub Form_Click()
  AutoRedraw = True: FontName = "Tahoma": Cls: Tag = Timer
 
  If Root Is Nothing Then
    Set Root = New cNode 'only Root-Nodes are created with the New-Operator (on the outside of cNode)
        Root.Text = "RootNode"
       
    AddChildNodesTo Root, 1000, "ChildLevel1_"
   
    Dim i As Long
    For i = 0 To Root.ChildCount - 1
        AddChildNodesTo Root.Child(i), 1000, "ChildLevel2_"
    Next
   
    Print "Construction-Time:", Format(Timer - Tag, " 0.00sec")
    Print "Total-NodeCount:", Root.TotalNodeCount
    Print "Root-ChildCount:", Root.ChildCount
    Print "ChildCount of a Level1-Child:  "; Root.FirstChild.ChildCount
    Print "ChildCount of a Level2-Child:  "; Root.FirstChild.FirstChild.ChildCount
    Print "Level-PrintOut:", Root.Level; Root.LastChild.Level; Root.LastChild.LastChild.Level
    With Root.LastChild.LastChild
        Print vbLf; "Infos for the Last ChildNode:"; vbLf; "  " & .Text
        Print "      IsChildOf: "; .Parent.Text
        Print "        IsChildOf: "; .Parent.Parent.Text
    End With
   
  Else
    Set Root = Nothing
    Print "Destruction-Time:", Format(Timer - Tag, " 0.00sec")
  End If
End Sub

Sub AddChildNodesTo(N As cNode, ByVal ChildCount As Long, TextPrefix As String)
  Dim i As Long
  For i = 0 To ChildCount - 1: N.AddNode TextPrefix & i: Next
End Sub

Have fun,

Olaf

Viewing all articles
Browse latest Browse all 1449

Trending Articles