Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
QB_Universal - Dynamic Memory System
#1
This system uses C++ vectors to bypass QB64’s UDT limitations. By storing a 64-bit handle inside your UDT, you create a gateway to dynamic memory. Use Var_New to initialize this container and Var_Push to instantly add strings or numbers without slow resizing. To retrieve data, Var_At navigates the index while specific getters read the values. This allows infinite nesting, where one handle holds a list of handles. Finally, Var_Free recursively destroys the entire tree, preventing 2026-era memory leaks. It transforms rigid Basic structures into flexible, modern data objects limited only by your physical RAM.

Download the attached files and put into your QB64 folder...

For @Pete : Arrays inside UDT's now! Also allows for arrays of UDTs inside UDTS!
Code: (Select All)
' ==========================================================
' WORD 95 DIALOGUE SYSTEM - 2026 FINAL STABLE PRO
' ==========================================================
'$DYNAMIC
'$INCLUDE:'qb_universal.bi'



_TITLE "Microsoft Word 95 Professional - 2026 Edition"
SCREEN _NEWIMAGE(800, 600, 32)

' Standard Declarations


' --- DATA INITIALIZATION ---
DIM SHARED MenuSystem AS _OFFSET
MenuSystem = Var_New

' Build File Menu
DIM FileMenu AS _OFFSET: FileMenu = BuildMenu("&File")
AddWordItem FileMenu, "&New...          Ctrl+N"
AddWordItem FileMenu, "&Open...        Ctrl+O"
AddWordItem FileMenu, "&Save            Ctrl+S"
AddWordItem FileMenu, "-"
DIM SendTo AS _OFFSET: SendTo = Var_New
Var_SetString SendTo, "Sen&d To          >"
AddWordItem SendTo, "Mail Recipient"
AddWordItem SendTo, "3.5 Floppy (ASmile"
Var_Push FileMenu, SendTo
AddWordItem FileMenu, "E&xit"

' Build Edit Menu
DIM EditMenu AS _OFFSET: EditMenu = BuildMenu("&Edit")
AddWordItem EditMenu, "&Undo          Ctrl+Z"
AddWordItem EditMenu, "-"
AddWordItem EditMenu, "Cu&t            Ctrl+X"
AddWordItem EditMenu, "&Copy          Ctrl+C"
AddWordItem EditMenu, "&Paste          Ctrl+V"

' Build View Menu
DIM ViewMenu AS _OFFSET: ViewMenu = BuildMenu("&View")
AddWordItem ViewMenu, "&Normal"
AddWordItem ViewMenu, "&Page Layout"
AddWordItem ViewMenu, "&Toolbars..."

' --- UI STATE ---
DIM SHARED TopSel AS INTEGER
DIM SHARED SubSel AS INTEGER
DIM SHARED MenuOpen AS _BYTE
DIM SHARED InSubMenu AS _BYTE
DIM SHARED LastAction AS STRING
LastAction = "Ready"

' --- MAIN APP LOOP ---
DO
  _LIMIT 60
  CLS
  COLOR _RGB32(0, 0, 0), _RGB32(192, 192, 192)
  PAINT (0, 0), _RGB32(192, 192, 192)

  DO WHILE _MOUSEINPUT
    mx = _MOUSEX: my = _MOUSEY: mb = _MOUSEBUTTON(1)
  LOOP

  ' UI Elements
  DrawAppWorkspace
  DrawMenuBar mx, my, mb

  IF MenuOpen THEN
    ' Bounds Safety Check
    IF TopSel >= 0 AND TopSel < Var_Count(MenuSystem) THEN
      DIM CurrentMenu AS _OFFSET
      CurrentMenu = Var_At(MenuSystem, TopSel)
      ' Spacing must match DrawMenuBar (80 pixels)
      menuX = TopSel * 80 + 5
      Draw95Dialogue CurrentMenu, menuX, 25, SubSel, InSubMenu, mx, my, mb
    END IF
  END IF

  k$ = INKEY$
  ProcessKeyboard k$

  _DISPLAY
LOOP UNTIL k$ = CHR$(27) AND NOT MenuOpen

Var_Free MenuSystem
SYSTEM

' --- SUBS AND FUNCTIONS ---

FUNCTION BuildMenu%& (Title AS STRING)
  DIM m AS _OFFSET: m = Var_New
  Var_SetString m, Title
  Var_Push MenuSystem, m
  BuildMenu = m
END FUNCTION

SUB AddWordItem (parent AS _OFFSET, text AS STRING)
  DIM n AS _OFFSET: n = Var_New
  Var_SetString n, text
  Var_Push parent, n
END SUB

SUB DrawAppWorkspace
  LINE (0, 580)-(800, 600), _RGB32(192, 192, 192), BF
  LINE (0, 580)-(800, 581), _RGB32(128, 128, 128), BF
  _PRINTSTRING (10, 584), "Action: " + LastAction
  LINE (50, 50)-(750, 570), _RGB32(255, 255, 255), BF
  LINE (50, 50)-(750, 570), _RGB32(0, 0, 0), B
END SUB

SUB DrawMenuBar (mx, my, mb)
  LINE (0, 0)-(800, 24), _RGB32(192, 192, 192), BF
  LINE (0, 24)-(800, 25), _RGB32(128, 128, 128), BF
  COLOR _RGB32(0, 0, 0), _RGBA32(0, 0, 0, 0)

  DIM total AS LONG: total = Var_Count(MenuSystem)
  FOR i = 0 TO total - 1
    DIM x AS INTEGER: x = i * 80 + 10
    DIM h AS _OFFSET: h = Var_At(MenuSystem, i)

    IF mx > x - 5 AND mx < x + 70 AND my < 25 THEN
      IF NOT MenuOpen THEN TopSel = i
      IF mb THEN
        MenuOpen = 1
        SubSel = 0
        InSubMenu = 0
      END IF
    END IF

    IF MenuOpen AND TopSel = i THEN
      LINE (x - 5, 2)-(x + 70, 22), _RGB32(0, 0, 128), BF
    END IF
    _PRINTSTRING (x, 5), Var_GetString$(h)
  NEXT
END SUB

SUB Draw95Dialogue (m AS _OFFSET, x, y, sel, inSub, mx, my, mb)
  DIM w AS INTEGER: w = 220
  DIM h AS INTEGER: h = Var_Count(m) * 22 + 10

  LINE (x, y)-(x + w, y + h), _RGB32(192, 192, 192), BF
  LINE (x, y)-(x + w, y + h), _RGB32(255, 255, 255), B
  LINE (x + 1, y + 1)-(x + w, y + h), 0, B
  LINE (x + 1, y + 1)-(x + w - 1, y + h - 1), _RGB32(128, 128, 128), B
  COLOR _RGB32(0, 0, 0), _RGBA32(0, 0, 0, 0)


  FOR i = 0 TO Var_Count(m) - 1
    DIM node AS _OFFSET: node = Var_At(m, i)
    t$ = Var_GetString$(node)
    yy = y + 5 + (i * 22)

    IF mx > x AND mx < x + w AND my > yy AND my < yy + 22 THEN
      IF NOT inSub THEN SubSel = i
      IF mb AND t$ <> "-" AND NOT INSTR(t$, ">") THEN
        LastAction = t$
        MenuOpen = 0
      END IF
    END IF

    IF t$ = "-" THEN
      LINE (x + 5, yy + 10)-(x + w - 5, yy + 11), _RGB32(128, 128, 128), BF
    ELSE
      IF i = sel AND NOT inSub THEN
        LINE (x + 4, yy)-(x + w - 4, yy + 20), _RGB32(0, 0, 128), BF
      END IF
      _PRINTSTRING (x + 15, yy + 4), t$
    END IF

    IF i = sel AND INSTR(t$, ">") AND Var_Count(node) > 0 THEN
      IF mx > x + w - 20 OR inSub THEN
        InSubMenu = 1
        Draw95Dialogue node, x + w - 5, yy, 0, 0, mx, my, mb
      END IF
    END IF
  NEXT
END SUB

SUB ProcessKeyboard (k$)
  IF k$ = "" THEN EXIT SUB
  IF k$ = CHR$(27) THEN MenuOpen = 0: InSubMenu = 0: EXIT SUB

  total = Var_Count(MenuSystem)
  IF MenuOpen AND total > 0 THEN
    mCount = Var_Count(Var_At(MenuSystem, TopSel))
    IF k$ = CHR$(0) + "P" THEN SubSel = (SubSel + 1) MOD mCount
    IF k$ = CHR$(0) + "H" THEN SubSel = (SubSel - 1 + mCount) MOD mCount
    IF k$ = CHR$(0) + "M" THEN TopSel = (TopSel + 1) MOD total: SubSel = 0: InSubMenu = 0
    IF k$ = CHR$(0) + "K" THEN TopSel = (TopSel - 1 + total) MOD total: SubSel = 0: InSubMenu = 0
  END IF
END SUB


And heres top trumps, basically demos how you can have various data types inside the same block...

Code: (Select All)

' --- [2] QB64 INTERFACE DECLARATIONS ---
'$INCLUDE:'qb_universal.bi'
' --- [3] INITIALIZE MASTER DECK ---
DIM SHARED MainDeck AS _OFFSET: MainDeck = Var_New
RESTORE SupercarData
DO
  READ n$
  IF n$ = "END" THEN EXIT DO
  READ spd&, z60#, pwr&, cyl&, cc&, rar&, value&
  DIM c AS _OFFSET: c = Var_New
  Var_SetString c, n$
  Var_Push c, Var_New: Var_SetInt Var_At(c, 0), spd& ' mph
  Var_Push c, Var_New: Var_SetDouble Var_At(c, 1), z60# ' 0-60
  Var_Push c, Var_New: Var_SetInt Var_At(c, 2), pwr& ' bhp
  Var_Push c, Var_New: Var_SetInt Var_At(c, 3), cyl& ' cylinders
  Var_Push c, Var_New: Var_SetInt Var_At(c, 4), cc& ' engine cc
  Var_Push c, Var_New: Var_SetInt Var_At(c, 5), rar& ' rarity
  Var_Push c, Var_New: Var_SetInt Var_At(c, 6), value& ' value
  Var_Push MainDeck, c
LOOP


SCREEN _NEWIMAGE(1024, 768, 32)
' --- [4] MAIN MENU & LOBBY ---
DIM SHARED PlayerCount AS INTEGER: PlayerCount = 1
DO
  _LIMIT 60: CLS

  _PRINTSTRING (380, 150), "SUPERCAR TOP TRUMPS: 1950-2026"
  _PRINTSTRING (420, 180), "LOADED CARS: " + STR$(Var_Count(MainDeck))
  FOR i = 1 TO 4
    IF PlayerCount = i THEN COLOR _RGB32(255, 255, 0) ELSE COLOR _RGB32(100, 100, 100)
    _PRINTSTRING (440, 250 + (i * 40)), "PLAYER LOBBY: " + STR$(i)
  NEXT
  k$ = INKEY$
  IF k$ = CHR$(0) + "H" THEN PlayerCount = PlayerCount - 1
  IF k$ = CHR$(0) + "P" THEN PlayerCount = PlayerCount + 1
  IF PlayerCount < 1 THEN PlayerCount = 4
  IF PlayerCount > 4 THEN PlayerCount = 1
  IF k$ = CHR$(13) THEN EXIT DO
  _DISPLAY
LOOP


' --- [6] THE 80-CARD RESEARCH DATASET ---
SupercarData:
' 1950s-60s
DATA "Jaguar XK120 (1950)",125,10.0,160,6,3442,5,150000
DATA "Mercedes-Benz 300 SL",163,8.8,215,6,2996,9,1850000
DATA "Ferrari 250 GTO (1962)",174,5.4,300,12,2953,10,72000000
DATA "Lamborghini Miura P400",171,6.7,350,12,3929,9,3600000
DATA "Shelby Cobra 427",164,4.2,425,8,7010,8,2200000
DATA "Ford GT40 MK I",164,4.5,335,8,4736,10,12000000
DATA "Aston Martin DB5",145,8.1,282,6,3995,7,1100000
DATA "Toyota 2000GT",135,8.6,150,6,1988,8,1200000
' 1970s-80s
DATA "Lamborghini Countach LP400",179,5.4,370,12,3929,9,1200000
DATA "Ferrari 512 BB",188,5.4,360,12,4943,7,350000
DATA "BMW M1",162,5.6,273,6,3453,8,750000
DATA "Ferrari F40 (1987)",201,3.8,471,8,2936,10,3900000
DATA "Porsche 959",197,3.7,444,6,2849,9,2300000
DATA "De Tomaso Pantera",159,5.5,330,8,5763,6,110000
DATA "Lotus Esprit Turbo",150,5.4,210,4,2174,5,85000
DATA "Lamborghini LM002",130,7.7,450,12,5167,8,400000
' 1990s-00s
DATA "McLaren F1 (1992)",240,3.2,627,12,6064,10,26000000
DATA "Jaguar XJ220",212,3.6,542,6,3498,7,600000
DATA "Bugatti EB110 SS",221,3.2,603,12,3500,9,3200000
DATA "Ferrari F50",202,3.7,513,12,4699,9,5000000
DATA "Lamborghini Diablo SV",202,3.8,510,12,5707,7,450000
DATA "Pagani Zonda C12",185,4.0,394,12,5987,8,6000000
DATA "Bugatti Veyron 16.4",253,2.5,987,16,7993,8,1900000
DATA "Ferrari Enzo",217,3.1,651,12,5998,9,4500000
DATA "Porsche Carrera GT",205,3.8,603,10,5733,8,1700000
DATA "Koenigsegg CCX",245,3.2,806,8,4700,7,3000000
DATA "Maserati MC12",205,3.7,621,12,5998,9,5000000
DATA "Saleen S7 Twin Turbo",248,2.8,750,8,7000,8,800000
' 2010s-20s
DATA "Lamborghini Aventador SVJ",217,2.8,759,12,6498,6,850000
DATA "Ferrari LaFerrari",217,2.4,950,12,6262,9,5200000
DATA "McLaren P1",217,2.8,903,8,3799,8,2200000
DATA "Porsche 918 Spyder",214,2.5,875,8,4593,8,2100000
DATA "Bugatti Chiron",261,2.4,1479,16,7993,7,3800000
DATA "Koenigsegg Jesko Absolut",330,2.5,1600,8,5000,9,4100000
DATA "Aston Martin Valkyrie",250,2.5,1160,12,6500,10,3500000
DATA "Rimac Nevera (EV)",258,1.74,1914,0,0,8,2400000
DATA "McLaren Senna",208,2.8,789,8,3994,7,1300000
DATA "Ferrari 812 Competizione",211,2.8,819,12,6496,7,800000
' 2026 Future Elite
DATA "Bugatti Tourbillon (2026)",276,2.0,1775,16,8300,10,4200000
DATA "Ferrari F80 (2026)",217,2.15,1184,6,2992,10,3900000
DATA "McLaren W1 (2026)",217,2.7,1258,8,3999,9,2100000
DATA "Yangwang U9 (2026)",233,2.36,1287,0,0,5,230000
DATA "Koenigsegg Gemera (2026)",248,1.9,2300,3,1988,8,1700000
DATA "Zenvo Aurora Tur (2026)",280,2.3,1850,12,6600,9,2800000
DATA "McMurtry Spéirling (2026)",190,1.55,1000,0,0,10,1100000
DATA "Tesla Roadster (2026 Est)",250,1.9,1200,0,0,6,250000
DATA "END",0,0,0,0,0,0,0
' --- [5] GAME ENGINE ---
GameLoop PlayerCount

SUB GameLoop (NumPlayers)
  _TITLE "Top Trumps: Game Session"
  DIM Category AS INTEGER: Category = 0
  DIM CurrentCard AS INTEGER: CurrentCard = 0
  DIM Status AS STRING: Status = "Player 1's Turn"
  DIM C AS _OFFSET

  ' Display Specs
  DIM Labels(0 TO 6) AS STRING
  Labels(0) = "Top Speed (mph):": Labels(1) = "0-60 mph (sec): "
  Labels(2) = "Power (BHP):    ": Labels(3) = "Cylinders:      "
  Labels(4) = "Displacement(cc):": Labels(5) = "Rarity (1-10):  "
  Labels(6) = "Value ($):      "

  DO
    _LIMIT 60: CLS
    COLOR _RGB32(255, 255, 255), _RGB32(20, 25, 45): CLS

    ' Draw Card Area
    LINE (300, 100)-(724, 650), _RGB32(240, 240, 240), BF
    LINE (310, 110)-(714, 640), 0, B

    ' Get Active Card
    C = Var_At(MainDeck, CurrentCard)
    COLOR _RGB32(0, 0, 0), _RGBA32(0, 0, 0, 0): _PRINTSTRING (350, 140), "CAR: " + Var_GetString$(C)
    _PRINTSTRING (350, 170), STRING$(30, "-")


    FOR i = 0 TO 6
      IF Category = i THEN COLOR _RGB32(200, 0, 0), _RGBA32(0, 0, 0, 0) ELSE COLOR _RGB32(0, 0, 0), _RGBA32(0, 0, 0, 0)
      _PRINTSTRING (340, 220 + (i * 40)), Labels(i)
      IF i = 1 THEN ' Double
        _PRINTSTRING (550, 220 + (i * 40)), STR$(Var_GetDouble(Var_At(C, i)))
      ELSE ' Ints
        _PRINTSTRING (550, 220 + (i * 40)), STR$(Var_GetInt(Var_At(C, i)))
      END IF
    NEXT

    COLOR _RGB32(255, 255, 255)
    _PRINTSTRING (10, 10), Status
    _PRINTSTRING (330, 680), "ARROWS: Select Category | ENTER: Play"

    k$ = INKEY$
    IF k$ = CHR$(0) + "H" THEN Category = (Category - 1 + 7) MOD 7
    IF k$ = CHR$(0) + "P" THEN Category = (Category + 1) MOD 7
    IF k$ = CHR$(13) THEN
      Status = "Selected Category " + STR$(Category + 1) + ". Dealing next..."
      CurrentCard = (CurrentCard + 1) MOD Var_Count(MainDeck)
      _DELAY 0.5
    END IF
    _DISPLAY
  LOOP UNTIL k$ = CHR$(27)
END SUB

Im gonna maybe finish that top trumps game....

Any ways...read the info file for detailed info and enjoy!

Unseen


Attached Files
.bi   QB_Universal.bi (Size: 799 bytes / Downloads: 10)
.h   QB_UNIVERSAL.h (Size: 2.09 KB / Downloads: 13)
.txt   QB_Universal Info.txt (Size: 7.55 KB / Downloads: 18)
Reply
#2
QB_SetString is unsafe. QB strings are not null-terminated. You should consider wrapping QB_SetString and adding the null-terminator at the end on the BASIC-side.

Also extern "C" and __declspec(dllexport) is really not required if you are planning to use this just as a header-only library.

You may consider "union" for the numeric types if you do not plan on using unique interger/float/ptr at the same time. That'll save some memory.

Looks good otherwise. Neat stuff.
Reply
#3
(01-16-2026, 09:02 AM)a740g Wrote: QB_SetString is unsafe. QB strings are not null-terminated. You should consider wrapping QB_SetString and adding the null-terminator at the end on the BASIC-side.
Good shout, i do usually use null termination but somehow forgot about it here! Can you explain the union thingy in bit more detail?

Thanks, and it took me and Googles AI about 8 hours of back and forth and 4 iterations to get this far.

john
Reply
#4
A union is a UDT that allows multiple members to share the same memory location.

For example:
Code: (Select All)
union UnionName {
    int intValue;
    float floatValue;
    char charValue;
};
All of the members above occupy the same memory, and the size of the union is determined by the largest member (in this case, typically 4 bytes).

I've made a few modifications to the library.

Cheers!


Attached Files
.zip   libvar.zip (Size: 6.26 KB / Downloads: 7)
Reply
#5
Yes, that's what I call a hit. This is exactly what we need. This is a huge step forward. This is absolutely great. I stopped my project so I could study this properly. Using nested arrays is absolutely great for both audio and video. I like the way Word creates menus. The only thing I don't know is - I couldn't find any information about _Preserve. If a nested array needs to be enlarged or reduced without losing data. Is that done automatically? No. Or is it? So we use _Preserve on the QB64PE side. But then - how does the memory overwrite on the C side happen? Do we have to destroy the entire structure first and reload it?

So here's how I see it:
Forget about TYPE with this method. You don't need it anymore.
and now I'll untangle a bit of the second program:

Code: (Select All)

Do
    Read n$
    If n$ = "END" Then Exit Do
    Read spd&, z60#, pwr&, cyl&, cc&, rar&, value& '                  how write this using TYPE  (not possible, just for orientation)
    Dim c As _Offset: c = Var_New '                                    Type C
  Var_SetString c, n$ '                                              n As String                            Type CAR
  Var_Push c, Var_New: Var_SetInt Var_At(c, 0), spd& ' mph            C() as CAR  here is nested field C()    spd As Long
  Var_Push c, Var_New: Var_SetDouble Var_At(c, 1), z60# ' 0-60        End Type                                z60 As Integer  but why  Var_SetDouble???
  Var_Push c, Var_New: Var_SetInt Var_At(c, 2), pwr& ' bhp                                                    pwr As Long
  Var_Push c, Var_New: Var_SetInt Var_At(c, 3), cyl& ' cylinders                                              cyl As Long
  Var_Push c, Var_New: Var_SetInt Var_At(c, 4), cc& ' engine cc                                              cc As Long
  Var_Push c, Var_New: Var_SetInt Var_At(c, 5), rar& ' rarity                                                rar as Long
  Var_Push c, Var_New: Var_SetInt Var_At(c, 6), value& ' value                                                value as Long
  Var_Push MainDeck, c '                                                                                      End Type                Dim MainDeck As C
LOOP

Another question, besides Preserve. Can an array with multiple elements be used as a nested array? (two, three dimensional)? Can you write example for this? 

Definitely this is really very useful. Thank you for sharing.


Reply
#6
@Petr

Hope this helps, also please feel free to mod/use the idea however you wish (Youre way better at C++ than me), my only hope is that something like this can finally allow us QB64'ers to have arrays (or at least simulate them) inside udts. 

The C++ backend uses dynamic vectors, so you do not need to use _Preserve or manually handle memory resizing. When you use Var_Push, the system automatically allocates more space and moves the data on the C side without any intervention required from the QB64 side. This means you never have to destroy and reload the entire structure just to change its size or add new elements. You only need to call Var_Free once on the main root parent when you are completely finished with the data, as it is designed to recursively clean up every nested child in the tree.

You can edit any specific value in an array without destroying or rebuilding the rest of the tree. By using Var_At to get the handle of a specific index, you can use the Var_Set functions to overwrite that data point instantly while leaving the surrounding structure intact. This same logic allows for multi-dimensional arrays; you simply nest Var objects inside other Var objects. For a 2D or 3D array, you push a "row" Var into a "matrix" Var, and you can then access specific coordinates by nesting your Var_At calls. This provides total flexibility for creating jagged arrays where each dimension can have a different number of elements.

I'll knock up some more demos if you need em (but i think youll be fine!)

Unseen
Reply


Forum Jump:


Users browsing this thread: