Recent

Author Topic: Copying records with dynamic arrays  (Read 4177 times)

jamie

  • Hero Member
  • *****
  • Posts: 6798
Re: Copying records with dynamic arrays
« Reply #15 on: January 25, 2025, 10:03:10 pm »
Repeat
Show the declaration for windowlist please
The only true wisdom is knowing you know nothing

TRon

  • Hero Member
  • *****
  • Posts: 3927
Re: Copying records with dynamic arrays
« Reply #16 on: January 25, 2025, 10:07:58 pm »
You can use copy but you seem to forget that your arrays are an array of an array. The array behind each array is not copied and requires a manual copy (or another copy). That is probably why ASerge mentioned the shallowness.

My head is starting to spin trying to keep track of that statement XD
Not the intention of the remark but I understand.

The code that was shown by LV solves that issue so you can also forget the statement I made. Perhaps take a look at it after you got things working.

fwiw: as per jamie, more code is always welcome in this kind of situations. I make the most stupid mistakes at complete other locations then where the error actually happens.
I do not have to remember anything anymore thanks to total-recall.

McDoob

  • New Member
  • *
  • Posts: 28
Re: Copying records with dynamic arrays
« Reply #17 on: January 25, 2025, 10:35:42 pm »
Ugh. I am such an idiot!   :-[

I found the bug, and it's really dumb. It was indeed located in the AddContent function. I'm so used to static arrays, and having them start at 1, that I seem to have forgotten that dynamic arrays start at 0...

Code: Pascal  [Select][+][-]
  1. procedure AddContent(window,x,y,color,bg:byte;text:string);
  2. //TODO: text wrapping
  3. var
  4.   cx,cy:byte;
  5.  
  6. begin
  7.   if not window=ActiveWindow then
  8.     error('AddContent to inactive window');
  9.   cx:=0;
  10.     repeat
  11. // ACCESS VIOLATION HERE
  12. //      WindowList[window].Window.Content[x+cx,y]:=text[cx+1];
  13. //      WindowList[window].Window.ContentColor[x+cx,y]:=color+(bg shl 4);
  14. // SOLUTION: subtract 1 from the array
  15.       WindowList[window].Window.Content[x-1+cx,y-1]:=text[cx+1];
  16.       WindowList[window].Window.ContentColor[x-1+cx,y-1]:=color+(bg shl 4);
  17.       cx:=cx+1;
  18.     until cx=length(text);
  19.   DrawContent(window);
  20.   UpdateScreen(false);
  21. end;
  22.  

The test program creates a series of windows that are only one line of ten characters. Therefore, the array would start at [0,0] and end at [0,9] and AddContent(ActiveWindow,1,1,lightgray,black,'Testing...') would start at [1,1] and NOT [0,0].

I literally slapped my forehead when I finally saw it. Consider this matter closed, but I'm still open to advice on how to streamline the use of these dynamic arrays...

fwiw: as per jamie, more code is always welcome in this kind of situations. I make the most stupid mistakes at complete other locations then where the error actually happens.

Uhh...yeah...that is exactly what happened here. A stupid mistake in a location I never even suspected...

On the plus side, this whole sidetrack did have positive results. My test program now uses less than half of the memory when running, from ~300KB to 132KB.

-McDoob
« Last Edit: January 25, 2025, 10:55:08 pm by McDoob »

TRon

  • Hero Member
  • *****
  • Posts: 3927
Re: Copying records with dynamic arrays
« Reply #18 on: January 25, 2025, 10:59:34 pm »
Consider this matter closed, but I'm still open to advice on how to streamline the use of these dynamic arrays...
It might perhaps be helpful to read/understand this wiki article. Especially the parts
Quote
A dynamic array’s definition will only allocate space for a pointer.

In order to define a multidimensional array, an array itself is specified as the base type, thus creating an “array of arrays”:

One quite useful fact is that, because multidimensional dynamic arrays are always “arrays of arrays”, the limitation that all dimensions must be of the same size does not apply to them. Different dimensions are implemented as arrays, and can each have their own size.

If I have to guess you would probably believe that your array in memory is stored as a grid with a horizontal and vertical dimension each cell containing your defined data. Nothing could be further from the truth as each array inside an array is merely a pointer in that first array and which itself again point to the actual content in memory.

How to streamline it ? To make it better maintainable I would probably create a special copy content function that copies the content from one content variable to another. If I would to implement what you seem to be doing I would probably make use of an advanced record for the content type using an array property for access and switch from a multidimensional to a single dimensional array. I would also be overloading the operators in that case.

Quote
Uhh...yeah...that is exactly what happened here. A stupid mistake in a location I never even suspected...
And we all have been there one time or another. Its like the mile high club so congratz  :)
« Last Edit: January 25, 2025, 11:08:15 pm by TRon »
I do not have to remember anything anymore thanks to total-recall.

McDoob

  • New Member
  • *
  • Posts: 28
Re: Copying records with dynamic arrays
« Reply #19 on: January 25, 2025, 11:08:25 pm »
It might perhaps be helpful to read/understand this wiki article. Especially the parts

If I have to guess you would probably believe that your array in memory is stored as a grid with a horizontal and vertical dimension each cell containing your defined data. Nothing could be further from the truth as each array inside an array is merely a pointer in that first array and which itself again point to the actual content in memory.

I did read through that article before I began this adventure, and it's actually where I got the idea. While I did understand that the 'first' array would actually be a pointer, it hadn't occurred to me that the second dimension would also be pointers. Your explanation really helps, thanks.

Quote
How to streamline it ? To make it better maintainable I would probably create a special copy content function that copies the content from one content variable to another. If I would to implement what you seem to be doing I would probably make use of an advanced record for the content type using an array property for access and switch from a multidimensional to a single dimensional array. I would also be overloading the operators in that case.

Quote
Uhh...yeah...that is exactly what happened here. A stupid mistake in a location I never even suspected...
And we all have been there on time or another. Its like the mile high club so congratz  :)

I would much rather join the mile high club. I've never had that chance, though I did date a stewardess for a little while in my earlier years.  :D

-McDoob

TRon

  • Hero Member
  • *****
  • Posts: 3927
Re: Copying records with dynamic arrays
« Reply #20 on: January 25, 2025, 11:31:54 pm »
I would much rather join the mile high club. I've never had that chance, though I did date a stewardess for a little while in my earlier years.  :D
It is a nicer experience, although in this day and age I wonder if there is even enough wiggle room  :)

BTW when working with arrays you can circumvent these kind of errors by using:
Code: Pascal  [Select][+][-]
  1.   for x := low(Myarray) to High(MyArray) do
  2.     for y := low(MyArray[x]) to high(Myarray[x] do
  3.     // copy contents.
  4.  

That way it does not matter whether your arrays are static or not and whether or not they begin at whatever index or not.
« Last Edit: January 26, 2025, 03:32:34 am by TRon »
I do not have to remember anything anymore thanks to total-recall.

Thaddy

  • Hero Member
  • *****
  • Posts: 16520
  • Kallstadt seems a good place to evict Trump to.
Re: Copying records with dynamic arrays
« Reply #21 on: January 26, 2025, 11:14:38 am »
It is enough to use system.copy()
Alas there are many different copy's all over the place.
Code: Pascal  [Select][+][-]
  1. function Copy(
  2.   A: DynArrayType;
  3.   Index: SizeInt;
  4.   Count: SizeInt
  5. ):DynArray;
  6.  
Use as:
Code: Pascal  [Select][+][-]
  1. var
  2.   a,b:array of integer;
  3. begin
  4.   b:=copy(a,0,length(a));
  5. end;
 
But I am sure they don't want the Trumps back...

McDoob

  • New Member
  • *
  • Posts: 28
Re: Copying records with dynamic arrays
« Reply #22 on: January 26, 2025, 03:23:42 pm »
BTW when working with arrays you can circumvent these kind of errors by using:
Code: Pascal  [Select][+][-]
  1.   for x := low(Myarray) to High(MyArray) do
  2.     for y := low(MyArray[x]) to high(Myarray[x] do
  3.     // copy contents.
  4.  

That way it does not matter whether your arrays are static or not and whether or not they begin at whatever index or not.

That's actually what I've done in my code in a couple of situations.

Initializing:
Code: Pascal  [Select][+][-]
  1.   with w do
  2.     begin
  3.       SetLength(Content, Size.X, Size.Y);
  4.       SetLength(ContentColor, Size.X, Size.Y);
  5.       for cx:=Low(Content) to High(Content) do
  6.         for cy:=Low(Content[cx]) to High(Content[cx]) do
  7.           Content[cx,cy]:=' ';
  8.       for cx:=Low(ContentColor) to High(ContentColor) do
  9.         for cy:=Low(ContentColor[cx]) to High(ContentColor[cx]) do
  10.           ContentColor[cx,cy]:=0;
  11.  

And copying:

Code: Pascal  [Select][+][-]
  1.   for cx := Low(w.Content) to High(w.Content) do
  2.     for cy := Low(w.Content[cx]) to High(w.Content[cx]) do
  3.       WindowList[AvailWindow].Window.Content[cx, cy] := w.Content[cx, cy];
  4.  
  5.   for cx := Low(w.ContentColor) to High(w.ContentColor) do
  6.     for cy := Low(w.ContentColor[cx]) to High(w.ContentColor[cx]) do
  7.       WindowList[AvailWindow].Window.ContentColor[cx, cy] := w.ContentColor[cx, cy];
  8.  

It hadn't occurred to me that the same could be used for static arrays, but it makes sense. I wonder if there's a way to modify my AddContent function to be transparent in this way. Right now, I have to manually subtract one in my array assignments for dynamic array use. I'll be trying to come up with a better solution for that today. Perhaps I can use Low() for that somehow...

-McDoob

jamie

  • Hero Member
  • *****
  • Posts: 6798
Re: Copying records with dynamic arrays
« Reply #23 on: January 26, 2025, 03:30:29 pm »
Ugh. I am such an idiot!   :-[

I found the bug, and it's really dumb. It was indeed located in the AddContent function. I'm so used to static arrays, and having them start at 1, that I seem to have forgotten that dynamic arrays start at 0...

Code: Pascal  [Select][+][-]
  1. procedure AddContent(window,x,y,color,bg:byte;text:string);
  2. //TODO: text wrapping
  3. var
  4.   cx,cy:byte;
  5.  
  6. begin
  7.   if not window=ActiveWindow then
  8.     error('AddContent to inactive window');
  9.   cx:=0;
  10.     repeat
  11. // ACCESS VIOLATION HERE
  12. //      WindowList[window].Window.Content[x+cx,y]:=text[cx+1];
  13. //      WindowList[window].Window.ContentColor[x+cx,y]:=color+(bg shl 4);
  14. // SOLUTION: subtract 1 from the array
  15.       WindowList[window].Window.Content[x-1+cx,y-1]:=text[cx+1];
  16.       WindowList[window].Window.ContentColor[x-1+cx,y-1]:=color+(bg shl 4);
  17.       cx:=cx+1;
  18.     until cx=length(text);
  19.   DrawContent(window);
  20.   UpdateScreen(false);
  21. end;
  22.  

The test program creates a series of windows that are only one line of ten characters. Therefore, the array would start at [0,0] and end at [0,9] and AddContent(ActiveWindow,1,1,lightgray,black,'Testing...') would start at [1,1] and NOT [0,0].

I literally slapped my forehead when I finally saw it. Consider this matter closed, but I'm still open to advice on how to streamline the use of these dynamic arrays...

fwiw: as per jamie, more code is always welcome in this kind of situations. I make the most stupid mistakes at complete other locations then where the error actually happens.

Uhh...yeah...that is exactly what happened here. A stupid mistake in a location I never even suspected...

On the plus side, this whole sidetrack did have positive results. My test program now uses less than half of the memory when running, from ~300KB to 132KB.

-McDoob
Right which why I kept asking you show your declaration of the windowlist.!
I already saw you were indexing starting at 1.
 Use low and high to determine the ranges
The only true wisdom is knowing you know nothing

McDoob

  • New Member
  • *
  • Posts: 28
Re: Copying records with dynamic arrays
« Reply #24 on: January 26, 2025, 03:38:52 pm »
Right which why I kept asking you show your declaration of the windowlist.!
I already saw you were indexing starting at 1.
 Use low and high to determine the ranges

Yes, thank you. That has been made clear to me.

Here's a preliminary change to AddContent that I've come up with. It works, but it seems...messy...
Code: Pascal  [Select][+][-]
  1. procedure AddContent(window,x,y,color,bg:byte;text:string);
  2. //TODO: text wrapping
  3. var
  4.   cx:byte;
  5.   arraycx, arraycy:integer;
  6. //  arrayccx,arrayccy:integer;
  7. begin
  8.   if not window=ActiveWindow then
  9.     error('AddContent to inactive window');
  10. // Original solution
  11. //  x:=x-1;
  12. //  y:=y-1;
  13.   arraycx:=Low(WindowList[Window].Window.Content)-1;
  14.   arraycy:=Low(WindowList[Window].Window.Content[Low(WindowList[Window].Window.Content)])-1;
  15. // This part shouldn't be necessary, as Content and ContentColor will always be the same size.
  16. //  arrayccx:=Low(WindowList[Window].Window.ContentColor)-1;
  17. //  arrayccy:=Low(WindowList[Window].Window.ContentColor[Low(WindowList[Window].Window.ContentColor)])-1;
  18.   cx:=0;
  19.     repeat
  20.       WindowList[window].Window.Content[arraycx+x+cx,arraycy+y]:=text[cx+1];
  21.       WindowList[window].Window.ContentColor[arraycx+x+cx,arraycy+y]:=color+(bg shl 4);
  22.       cx:=cx+1;
  23.     until cx=length(text);
  24.   DrawContent(window);
  25.   UpdateScreen(false);
  26. end;
  27.  

There aren't any alignment errors, and definitely no access violations, at least with dynamic arrays. I don't intend to go back to static ones, so I haven't tested them.

As an aside, an apparent alignment error in DrawContent (the text of Content was being written one line up and one character to the left of expected) is actually what made me realize where the first bug actually was. DrawContent had been using Low() and High() ever since I started (note the +1 in each array assignment):

Code: Pascal  [Select][+][-]
  1. procedure DrawContent(w:byte);
  2.  
  3. var
  4.   cx,cy:byte;
  5.  
  6. begin
  7.   with WindowList[w].Window do
  8.     for cx:=Low(Content) to High(Content) do
  9.       for cy:=Low(Content[cx]) to High(Content[cx]) do
  10.         TextOut(Origin.X+1+cx,Origin.Y+1+cy,ContentColor[cx,cy],(ContentColor[cx,cy] shl 4),Content[cx,cy]);
  11. end;

So far, so good!
-McDoob
« Last Edit: January 26, 2025, 03:48:24 pm by McDoob »

McDoob

  • New Member
  • *
  • Posts: 28
Re: Copying records with dynamic arrays
« Reply #25 on: January 26, 2025, 03:58:38 pm »
I've changed AddContent to use if..then statements to conditionally subtract one from the x and y inputs. This feels a lot cleaner to me:
Code: Pascal  [Select][+][-]
  1. procedure AddContent(window,x,y,color,bg:byte;text:string);
  2. //TODO: text wrapping
  3. var
  4.   cx:byte;
  5. begin
  6.   if not window=ActiveWindow then
  7.     error('AddContent to inactive window');
  8.   if (Low(WindowList[Window].Window.Content)=0) then x:=x-1;
  9.   if (Low(WindowList[Window].Window.Content[x])=0) then y:=y-1;
  10.   cx:=0;
  11.     repeat
  12.       WindowList[window].Window.Content[x+cx,y]:=text[cx+1];
  13.       WindowList[window].Window.ContentColor[x+cx,y]:=color+(bg shl 4);
  14.       cx:=cx+1;
  15.     until cx=length(text);
  16.   DrawContent(window);
  17.   UpdateScreen(false);
  18. end;
I've also done something similar in DrawContent:
Code: Pascal  [Select][+][-]
  1. procedure DrawContent(w:byte);
  2.  
  3. var
  4.   cx,cy,addx,addy:byte;
  5.  
  6. begin
  7.   addx:=0;
  8.   addy:=0;
  9.   with WindowList[w].Window do
  10.     begin
  11.       if (Low(Content)=0) then addx:=1;
  12.       if (Low(Content[Low(Content)])=0) then addy:=1;
  13.       for cx:=Low(Content) to High(Content) do
  14.         for cy:=Low(Content[cx]) to High(Content[cx]) do
  15.           TextOut(Origin.X+addx+cx,Origin.Y+addy+cy,ContentColor[cx,cy],(ContentColor[cx,cy] shl 4),Content[cx,cy]);
  16.     end;
  17. end;
-McDoob
« Last Edit: January 26, 2025, 04:07:19 pm by McDoob »

alpine

  • Hero Member
  • *****
  • Posts: 1343
Re: Copying records with dynamic arrays
« Reply #26 on: January 26, 2025, 10:18:24 pm »
*snip*
I've been banging my head against this wall for a couple of days now. I could always go back to the static arrays, but 80x25 arrays for every window, including tiny ones, seems really wasteful, and totally against the grain for my goal of creating a tiny-footprint text-mode windowed UI for use with other projects.
Is there any particular reason to use string[1] as a base type for the ContentType definition?
It will include an additional (unneeded) byte for the actual length of the string, which in your case will be always one.

I would use a one-dimensional array and do the coordinate calculation in place, this way it seems even much simpler to me:
Code: Pascal  [Select][+][-]
  1.   ContentType = array of Char;
  2.   ContentColorType = array of Byte;
  3.   WindowType = record
  4.     DoubleBorder:boolean;
  5.     Origin,Size:XYType;
  6.     Content:ContentType;
  7.     ContentColor:ContentColorType;
  8.     Title:string[MaxTitleLength];
  9.   end;

Then you can allocate by: SetLength(Content, Size.X * Size.Y)
if you have (x,y) coordinate then the corresponding cell will be: Content[y * Size.X + x]
And the corresponding coordinates for the Content[ I ] will be ({x=}I mod Size.X, {y=}I div Size.X)

(assuming zero based x,y coordinates)

This will save all the trouble about shallowness and references in dynamic arrays. Also will save a small amount of memory used for keeping lengths and refcounts of the second dimension.

Of course, there is no need to use div/mod everywhere, eg. for drawing the contents you can just introduce an additional variable, z, and increment it into the inner cycle:
Code: Pascal  [Select][+][-]
  1.         z := 0;
  2.         for y:=1 to Size.Y do
  3.           for x:=1 to Size.X do
  4.           begin
  5.               if not (Content[z]='') then
  6.                 TextOut(Origin.X+x,Origin.Y+y,ContentColor[z],(ContentColor[z] shl 4),Content[z]);
  7.               Inc(z);
  8.           end;
  9.  

« Last Edit: January 27, 2025, 09:53:38 am by alpine »
"I'm sorry Dave, I'm afraid I can't do that."
—HAL 9000

McDoob

  • New Member
  • *
  • Posts: 28
Re: Copying records with dynamic arrays
« Reply #27 on: January 27, 2025, 03:04:42 pm »
Is there any particular reason to use string[1] as a base type for the ContentType definition?
It will include an additional (unneeded) byte for the actual length of the string, which in your case will be always one.

No, not really. I'm pretty sure I can use char in place of that. I think I started that way, and switched to string[1] for some sort of debugging? I don't remember why I switched, to be honest...I don't see why I couldn't switch back.

Quote
I would use a one-dimensional array and do the coordinate calculation in place, this way it seems even much simpler to me:

Then you can allocate by: SetLength(Content, Size.X * Size.Y)
if you have (x,y) coordinate then the corresponding cell will be: Content[y * Size.X + x]
And the corresponding coordinates for the Content[ I ] will be ({x=}I mod Size.X, {y=}I div Size.X)

(assuming zero based x,y coordinates)

This will save all the trouble about shallowness and references in dynamic arrays. Also will save a small amount of memory used for keeping lengths and refcounts of the second dimension.

Of course, there is no need to use div/mod everywhere, eg. for drawing the contents you can just introduce an additional variable, z, and increment it into the inner cycle:

Interesting. I believe this is how the video unit, and VideoBuf works. The TextOut proc, which I sourced (and slightly modified) from the online documentation on the video unit, had some maths in it that I didn't understand at all, but makes a bit more sense after reading your post:
Code: Pascal  [Select][+][-]
  1. procedure TextOut(x,y,fg,bg:Word;Const s:String);
  2.  
  3. Var
  4.   P,I,M : Word;
  5.  
  6. begin
  7.   P:=((X-1)+(Y-1)*ScreenWidth);
  8.   M:=Length(S);
  9.   If (P+M)>ScreenWidth*ScreenHeight then
  10.     M:=ScreenWidth*ScreenHeight-P;
  11.   For I:=1 to M do
  12.     VideoBuf^[P+I-1]:=Ord(S[i])+(fg shl 8)+(bg shl 12);
  13. end;
  14.  

I don't know how you could think this is 'simpler', though. I could see myself making a lot of mistakes trying to implement this change. How much memory do you think this would save? That is, after all, one of the goals of this dynamic array endeavor.

In other news, I've updated my code to make WindowList[] a dynamic array, as well. I did away with the InUse variable at the same time, as it wouldn't be necessary. Therefore, WindowList is now just an array of WindowType whose length defines the number of active windows intrinsically. I had to make use of the Delete() function in DestroyWindow (the entire proc also became a ton simpler), which wasn't too difficult, and had to make changes to any previous reference to WindowList[], which took quite a while. But it's bug-free!

Unfortunately, I didn't see any significant change in the memory footprint. I guess that array wasn't really using much to begin with. Today, I'll be doing some housekeeping on the code, cleaning up all the commented out code I'm no longer using, and making sure everything I am using actually makes sense.

-McDoob

EDIT: I switched Content to array of array of char with absolutely no problems. I really don't know why I was using string[1]...

That's strange...my code's memory footprint has doubled? It's now using 262kb from 131...Things that make me go 'Hmm' #1234, I guess...
« Last Edit: January 27, 2025, 03:17:52 pm by McDoob »

TRon

  • Hero Member
  • *****
  • Posts: 3927
Re: Copying records with dynamic arrays
« Reply #28 on: January 27, 2025, 03:17:08 pm »
I don't know how you could think this is 'simpler', though. I could see myself making a lot of mistakes trying to implement this change. How much memory do you think this would save? That is, after all, one of the goals of this dynamic array endeavor.

Unfortunately, I didn't see any significant change in the memory footprint. I guess that array wasn't really using much to begin with. Today, I'll be doing some housekeeping on the code, cleaning up all the commented out code I'm no longer using, and making sure everything I am using actually makes sense.
T'is not about the memory footprint. Using a single dimensional array reduces memory fragmentation, allows for a single move to copy it contents and in the end is easier to maintain.

...If I would to implement what you seem to be doing I would probably make use of an advanced record for the content type using an array property for access and switch from a multidimensional to a single dimensional array. I would also be overloading the operators in that case.
The calculation might perhaps seem more difficult then you are using now but that is where the array property comes in place (which would allow for accessing using the x and y coordinates but behind the scenes calculate the offset in the single dimensional array. That way it is possible to use the fast move to make a backup/restore of the background but still use the traditional access for 'precision' access.

It is all but a suggestion in case asked (which you did). There is not a right or wrong just some pointer on how things could be improved. It might very well be overkill for your use-case. You are the only one who is able to judge/weigh that.
I do not have to remember anything anymore thanks to total-recall.

McDoob

  • New Member
  • *
  • Posts: 28
Re: Copying records with dynamic arrays
« Reply #29 on: January 27, 2025, 03:24:51 pm »
I don't know how you could think this is 'simpler', though. I could see myself making a lot of mistakes trying to implement this change. How much memory do you think this would save? That is, after all, one of the goals of this dynamic array endeavor.

Unfortunately, I didn't see any significant change in the memory footprint. I guess that array wasn't really using much to begin with. Today, I'll be doing some housekeeping on the code, cleaning up all the commented out code I'm no longer using, and making sure everything I am using actually makes sense.
T'is not about the memory footprint. Using a single dimensional array reduces memory fragmentation, allows for a single move to copy it contents and in the end is easier to maintain.

I think you may have misunderstood me there. I haven't implemented the single-dimension approach yet. I was talking about the difference after converting WindowList[] to dynamic.

Quote
...If I would to implement what you seem to be doing I would probably make use of an advanced record for the content type using an array property for access and switch from a multidimensional to a single dimensional array. I would also be overloading the operators in that case.
The calculation might perhaps seem more difficult then you are using now but that is where the array property comes in place (which would allow for accessing using the x and y coordinates but behind the scenes calculate the offset in the single dimensional array. That way it is possible to use the fast move to make a backup/restore of the background but still use the traditional access for 'precision' access.

It is all but a suggestion in case asked (which you did). There is not a right or wrong just some pointer on how things could be improved. It might very well be overkill for your use-case. You are the only one who is able to judge/weigh that.

Don't get me wrong. I am very much open to suggestions! But, I'll be the first to admit that I'm a relative newbie in coding. I'm basically trying to pick up where I left off in high school, some quarter century ago. A lot of these concepts are just a bit beyond my skill level. I don't even understand what an 'array property' means. Do you think you could provide an example?

BTW, thanks TRon. You've already been a big help!

-McDoob

 

TinyPortal © 2005-2018