Adventure Game Studio

AGS Development => Engine Development => Topic started by: Crimson Wizard on Thu 15/07/2021 11:35:59

Title: Supporting pointers in managed structs
Post by: Crimson Wizard on Thu 15/07/2021 11:35:59
UPDATED in 2023

Test build: https://cirrus-ci.com/task/5586565305466880
Ticket: https://github.com/adventuregamestudio/ags/pull/1923

UPDATED later in 2023

This is now fully merged into ags4 branch.




It looks like we may be few steps away from actually supporting managed pointers inside managed structs.

To clarify, since not everyone knows why they are not supported, the problem is that engine does not know real contents of the struct at runtime, and does not know which members are pointers.
Because of that it cannot deduce if something inside struct also has to be released. And if there were a managed struct with pointers, these will not get deleted, creating a so called memory leak. Basically, this may lead to program crashing with "out of memory" error, because objects to which these pointers-in-struct point never get deleted.
So we had to artificially forbid them.

The proposed solution was to introduce a so called RTTI - table containing description of all the types in the game script. This table would not only tell which types are there, but also, what members do they have, and which types these members are. Specifically for this problem this description must tell which of these members are pointers.
A related ticket: https://github.com/adventuregamestudio/ags/issues/1259


I did a small experiment for fun, generating such table along with the script data, and letting engine consult it when disposing managed structs.
https://github.com/ivan-mogilko/ags-refactoring/commits/experiment--rtti

And it works - if you create a chain of managed structs in script - it correctly deletes everything when necessary without any memory leaks.

The code above is extremely dirty though, so it will take time to prepare a polished version.
Title: Re: (Upcoming) experiment of supporting pointers in managed structs
Post by: eri0o on Thu 15/07/2021 13:18:00
Hey, could this give some C# like serialization capabilities? (imagining once generics are a thing)
Title: Re: (Upcoming) experiment of supporting pointers in managed structs
Post by: Crimson Wizard on Thu 15/07/2021 13:51:41
Quote from: eri0o on Thu 15/07/2021 13:18:00
Hey, could this give some C# like serialization capabilities? (imagining once generics are a thing)

Could you give an example of what you are refering to?

Hypothetically, RTTI means reflection, and as a consequence - watching variable values. In perspective - also virtual function tables, but I am not looking so far atm.
Oh, and of course, dynamic type casting - from parent to child.

In regards to generics - no idea if that will be ever possible to implement for ags script, or if it is a suitable language for that sort of thing. But some kind of "any-type" symbol would be nice... like "any dynamic array" for instance, that could be sent into specialized api. Just imagining things.
Title: Re: (Upcoming) experiment of supporting pointers in managed structs
Post by: eri0o on Thu 15/07/2021 15:23:34
What I meant is if it's possible to be aware in the script of what are the types of struct members, well, it would be possible to traverse them and get each member to workout the serialization, in script - Google's protobuf does this in it's C# implementation, but uses access to the  Marshall Memory after picking the references and sizeof to figure things out.

Generics are the proper way to ensure type safety - among other things, auto-complete is a lot easier to make it work with them.

QuoteRTTI means reflection

Reflection would be huge but this would mean storing variable names too? I think this would be information that is not needed to store. I think Java does by only storing public members of classes (not sure though), see http://openjdk.java.net/jeps/118
Title: Re: (Upcoming) experiment of supporting pointers in managed structs
Post by: fernewelten on Thu 15/07/2021 17:54:14
Quote from: Crimson Wizard on Thu 15/07/2021 11:35:59
It looks like we may be few steps away from actually supporting managed pointers inside managed structs.
:-D :-D

(https://www.loquimur-online.de/ags/talk/applause.gif)
Title: Re: (Upcoming) experiment of supporting pointers in managed structs
Post by: Crimson Wizard on Fri 16/07/2021 11:38:22
Bit unrelated, but I've been randomly thinking about all that reflection stuff, and also this feature proposal by fernewelten (https://github.com/adventuregamestudio/ags/issues/1363), and it suddenly came to me that there may be a relatively non-complicated way of implementing a variable watch in the Editor.

Assuming the editor is running a game that it just compiled, it may have a saved table of variables from the compiler (or rather - from tokenizer - a first compilation stage). That gives a mapping between variable names and their memory offsets. Editor may keep that in memory or rather save it to a file somewhere in its workspace for the future use.

When user is running a test, and wants to know current value of a variable, Editor would pass a command into the engine, asking for a value at a certain offset in the script memory (global or function stack), and engine would just return that.
Title: Re: (Upcoming) experiment of supporting pointers in managed structs
Post by: fernewelten on Tue 03/08/2021 07:06:31
I've been thinking about Crimson's idea a little.

Here's a very tentative and high-level idea that builds on this.

Currently, the Editor is holding a complete copy of the Compiler.
When a program is run in debug mode and halted, then the Editor is running.
This means that the Compiler is available at that point in time and can be called.
It might "compile" an expression such as "Weapon[15].Shield.Damage",
and then you'd get the Bytecode for it, i.e., a step-for-step instruction for the Engine of just how to reach that value at this point in time.
The Engine already knows how to run Bytecode.
It could be configured to save its current state (content of the registers and so on) and then temporarily execute the Bytecode of the expression.
Then it would have the expression result in AX (or in the memory cell that MAR points to), ready to be passed to the Editor to be displayed.
Afterwards, it would retrieve its current state and continue running.

Now we don't need to see the compiler as a cumbersome and heavy-weight blob of software that can only deal with complete programs:
it consists of several separate classes that can operate independently, such as a scanning class..
These parts of the compiler that specifically deal with expressions could be further split off, giving an "expression parser class".
The compiler itself would use this "expression parser class", and the "expression parser class" could also be used independently.
That's what would happen at debugging time:
For each expression to be watched, the debugging mode of the Editor would call the scanner class and then the "expression parser class".

It's inevitable that the compiler will evolve over time.
Modifications will be made, new features will be added, and so on.
In these cases, the "debugger" will neatly follow suit.
It uses the same parts that the compiler uses, so when the compiler changes, the debugger changes.
So the "debugger code" can't become out ouf sync to the "compiler code".

This is an advantage of the approach. But this can also be a disadvantage:
The compiler would no longer be free to do whatever it wants to.
There would be an interface that the debugger uses,
and that interface would need to remain rather stable.
And so the compiler would also need to be rather stable, as concerns calling this interface.

An expression such as  "Weapon[15].Shield.Damage" changes its meaning dependent on the context.
For instance, "Weapon" might be a local variable at some point in time, and another local variable at another point in time.
So we will need to construct the context at the point that is being debugged.
When the compiler runs its proper compiling run, it has this context available: When it's in the middle of compiling some function, it knows exactly what variables are at what stack offsets and so on.
When the debugger runs and wants to query a variable, this context must be reconstructed.
For instance, when the debugger halts, e.g., on line 42 of "globalscript.asc", then the context at this line 42 must be reconstructed.
This means that the compiler must write the various contexts into, e.g., a debugging file in the proper compiling run,
and a function such as "recall_context(debugging_file, "globalscript.asc", 42)" must be provided that the debugger can use.

So in total, when the debugger halts alt line 42 of file "globalscript.asc" and wants to know the value of "Weapon[15].Shield.Damage", it must;
- call recall_context(debugging_file, "globalscript.asc", 42)
- call expression_parse(scan("Weapon[15].Shield.Damage"))
- pass the resulting Bytecode to the Engine for executing
- the Engine must save its current state,
- execute the Bytecode
- report AX (or the value of the memory that MAR points to)
- restore its current state

------

We have:
the compiler
the Engine

We need
- to split off the expression parser from the compiler. This is time-consuming, but will strongly improve the compiler (it will become more modularized and easier to modify and extend).
- to make the compiler write the context information. This information is available in the symbol table. It gets there incrementally, so at the point where a local variable is added or invalidated, this can also be written into the "debugging file". This part is probably fairly easy.
- to write recall_context(). One way of going about it is to start with a given context, e.g., at the start of a function, and then "replay" the context steps until the line in question is reached. This can probably be done with middling effort.
- The last two steps must be developed in tandem, of course.

- to prepare the Engine for
    = saving its state, executing "temporary" bytecode, restoring its state
    = passing register AX (or the value that MAR points to) to the Editor
I don't know enough of the Engine to make educated guesses on how cumbrous this is.

By the way, if the compiler can find an instruction sequence that makes MAR point to the memory that contains the value, then it is possible in principle to "patch in" another value at debugging time. So variables can not only be watched but even modified. This may open a can of worms, so we'd better not do this right from the beginning. But it's a perspective.
Title: Re: (Upcoming) experiment of supporting pointers in managed structs
Post by: eri0o on Wed 04/08/2021 01:56:37
Isn't it possible to generate something like a .pdb instead of reusing the compiler at runtime? Like a symbol map or a DWARF file.
Title: Re: (Upcoming) experiment of supporting pointers in managed structs
Post by: fernewelten on Sun 08/08/2021 12:42:16
Quote from: Crimson Wizard on Thu 15/07/2021 11:35:59
I did a small experiment for fun, generating such table along with the script data, and letting engine consult it when disposing managed structs.
https://github.com/ivan-mogilko/ags-refactoring/commits/experiment--rtti

Having a look to see just what the compiler would need to provide.
Title: Re: (Upcoming) experiment of supporting pointers in managed structs
Post by: fernewelten on Sun 08/08/2021 13:32:30
So currently, what I basically have as type information in the symbol table is this:

A type is either atomic
or compound

There's no built-in restriction in the symbol table itself that compound types must consist of atomic types. Compound types can consist of compound types. (But the compiler ensures with code that the types are limited by what the engine can currently handle. For instance, no dynarrays of structs that contain pointers and non-pointers.)

That's what has already been implemented, i.e., what the (new) compiler is currently working with.
Title: Re: (Upcoming) experiment of supporting pointers in managed structs
Post by: fernewelten on Sun 08/08/2021 13:49:44
What the engine needs of all this is only in structs, at what offsets the pointers are and what the types of thes pointers are.

We could store this separately for the benefit of the engine as a compact list of (offset, type-id), e.g.,
<id> -->
<number_of_entries>
<offset_1><type_id_1>
<offset_2><type_id_2>
....
<offset_n><type_id_n>
Title: Re: (Upcoming) experiment of supporting pointers in managed structs
Post by: Monsieur OUXX on Thu 02/09/2021 22:43:48
A bit late to the party, but: generics are hard to implement (at least syntactically in the compiler).
Whereas there's a much more reachable goal post:  an untyped pointer type (for example: Pointer*) that supports casting. This is how genericity was achieved in early languages since ever -- before C++ had templates and so on.  (void* in C, Object in early Javas... Even object in C# if you're a psychopath).

This idea is already partially implemented in AGS when you have a GUIControl* and you cast it to Button*, Label*, etc.


Apart from that I am extremely excited to put my hands on pointers in managed structs.
Title: Re: (Upcoming) experiment of supporting pointers in managed structs
Post by: Crimson Wizard on Wed 01/02/2023 19:26:26
As a heads up, I consider the RTTI (and expanding the managed pointers support in particular) my priority task after 3.6.0 release. So, I return to this issue, and posted an update on a RTTI table format in the ticket:
https://github.com/adventuregamestudio/ags/issues/1259#issuecomment-1412573652
Title: Re: (Upcoming) experiment of supporting pointers in managed structs
Post by: eri0o on Sat 04/02/2023 12:57:03
Is there a way to have the table of names be built separately so it's not part of the release build? I think the names are not needed unless the game is being debugged? Maybe not consider this on the first interaction of the feature, but something to consider at some point.
Title: Re: (Upcoming) experiment of supporting pointers in managed structs
Post by: Crimson Wizard on Sat 04/02/2023 14:19:44
Quote from: eri0o on Sat 04/02/2023 12:57:03Is there a way to have the table of names be built separately so it's not part of the release build? I think the names are not needed unless the game is being debugged? Maybe not consider this on the first interaction of the feature, but something to consider at some point.

Technically, the strings may be skipped, because with the updated format (as described in the latest comment in the ticket) the strings are saved in a separate table, so it's a matter of not generating this data during compilation, and skipping the step during writing.

The problem is though that the full type names are used as global keys when merging rtti tables from different script units. Either something else should be used to identify types globally, or these names may be obfuscated.
Title: Re: (Upcoming) experiment of supporting pointers in managed structs
Post by: eri0o on Sat 04/02/2023 14:47:12
Uhm, do the builtin types (Character, Object, ...) also have this type name? And say, a built-in type from a plugin.

Just trying to figure out what happens with things that have no "script unit" - imagining rtti is already in and I am looking at a variable through the new rtti powered AGS debugger.
Title: Re: (Upcoming) experiment of supporting pointers in managed structs
Post by: Crimson Wizard on Sat 04/02/2023 15:08:47
Quote from: eri0o on Sat 04/02/2023 14:47:12Uhm, do the builtin types (Character, Object, ...) also have this type name? And say, a built-in type from a plugin.

Just trying to figure out what happens with things that have no "script unit" - imagining rtti is already in and I am looking at a variable through the new rtti powered AGS debugger.

Their "unit" is actually called a "BuiltinScriptHeader" (or something like that).
I imagine plugin's "unit" will also have some generated name like that.

By the way, it is already possible to see these names through a program debugger, if using my new wip branch:
https://github.com/ivan-mogilko/ags-refactoring/tree/ags4--rtti

There's a number of debug fields I added that work as a cross-references between types and fields, and let explore the table after it's built by compiler.
(I think I move these to separate struct later)
Title: Re: (Upcoming) experiment of supporting pointers in managed structs
Post by: Crimson Wizard on Wed 15/02/2023 09:37:08
Opened two consecutive pull requests (second depends on the first):

RTTI: https://github.com/adventuregamestudio/ags/pull/1922
Support managed pointers inside managed structs: https://github.com/adventuregamestudio/ags/pull/1923

Downloadable temp build:
https://cirrus-ci.com/task/4863501278117888

With this test build you should be able to have pointers inside managed structs, therefore any kind of struct relations. Deleting these structs should not cause memory leaks (the program's memory use should go down if you delete them).
There are still some things not complete, like, managed structs get broken after restoring a save, but that because I did not fix that yet.
Well, see the ticket for the full description of the situation.

EDIT: Oh, another thing, this build also supports "--print-rtti" command line arg, which tells the engine to dump type information into the log.
For instance, you may run your game from command line as:
"gamename.exe --log-file=all --print-rtti" and the type table will be printed in the engine log (e.g. "%USERPROFILE%\Saved Games\Adventure Game Studio" on Windows).
Title: Re: (Upcoming) experiment of supporting pointers in managed structs
Post by: Crimson Wizard on Sat 08/04/2023 01:20:09
An update: fingers crossed, the saves problem seem to be fixed to some "minimal necessary" degree.
(details are in comments to the ticket https://github.com/adventuregamestudio/ags/pull/1922)

The latest code I wrote there is quite ugly, so I will take couple more days cleaning it up, and testing.

There are still certain things that you may do to managed structs that will break older saves. There may be potential solutions, but not all of these are worth considering at the moment (too much work, and likely not much use on its own).

For example, the biggest break is if you change the order of member variables inside a struct, and pointers will appear at different offsets. This breaks the structs in saves even without pointers, actually.
Title: Re: Supporting pointers in managed structs
Post by: Crimson Wizard on Mon 10/04/2023 19:50:38
Updated again. There's a working solution. Can't tell if most optimal one, but it formally works.
Technical details are here: https://github.com/adventuregamestudio/ags/pull/1923#issuecomment-1502145700

May be downloaded and tested with this build (UPDATED 17th april):
https://cirrus-ci.com/task/4872943738552320

WARNING:
* Changes compiled game format and save format;
* Based on AGS 4 experimental version, which means that it will irreversibly update your game project too if you are coming from AGS 3.*. Better test on a project's copy.
* If you are already a AGS 4 user, this does NOT change the AGS 4 project format further, so it's safe to test on your real projects, except you'll have to fully rebuild if changing to a previous AGS 4; also the new engine will produce incompatible saves.
Title: Re: Supporting pointers in managed structs
Post by: Crimson Wizard on Tue 18/04/2023 18:39:44
Unfortunately, this is still not done. There's another problem which I forgot about: it's circular dependencies. The objects not referenced from script may still reference each other, which keeps them in memory until the program ends.

I never researched how this is done in other managed languages, but from a quick check they somehow are able to distinguish whether reference is from a script or internally from an object, and then consider every object that does not have a "script" reference a garbage amd remove it during garbage collection.

Need to invent something similar for AGS script now.

I really wish someone mentioned this earlier to me! Maybe it's going to be even larger work than reflection with RTTI... which means this functionality is not as close to completion as i supposed.
Title: Re: Supporting pointers in managed structs
Post by: Crimson Wizard on Tue 18/04/2023 20:27:50
Speaking theoretically, the GC should be able to detect, which managed objects are referenced from within the normal script memory, and which are not. If an object is not referenced from the script mem, - such object is detached and considered garbage.

NUANCE: engine may also hold "internal" managed references, so GC must also take that into account (not certain how yet).

Supposing we only run GC between the scripts, during the game frame update. That means that the only place we need to check is global script memory (because stack will be reset at the time). If the script memory would have contained hints on the data types in it, we could know where are "dynamic object handles" among it.

Hypothetically, the GC process then may work like this:

* scan the global script memory;
* for each value in that memory, which has a "dynamic handle" type,
   * mark that handle as used;
   * retrieve the dynamic object itself, find out if there are managed handles inside too, mark these as used;
   * repeat recursively for each connected dynamic object?

* after all this, compare list of existing handles with the list of "used" ones, and dispose objects with "unused" handles.
Title: Re: Supporting pointers in managed structs
Post by: eri0o on Tue 18/04/2023 21:40:01
I don't think I get quite well what circular dependency case you mean here, can you explain it with some example?

I am curious if it's something that would be possible to detect at compile time and just not allow for the time being.

QuoteSupposing we only run GC between the scripts, during the game frame update

Would the GC not need to happen on each scope change? Or would this only affect the circular dependent object?
Title: Re: Supporting pointers in managed structs
Post by: Crimson Wizard on Tue 18/04/2023 21:49:18
Quote from: eri0o on Tue 18/04/2023 21:40:01I don't think I get quite well what circular dependency case you mean here, can you explain it with some example?

Simply when two objects reference each other, directly or indirectly.

Code (ags) Select
// Example 1
struct LinkedItem {
    LinkedItem* next;
    LinkedItem* prev;
};

// Example 2
struct Parent {
    Child* children[];
};

struct Child {
    Parent* parent;
};



Quote from: eri0o on Tue 18/04/2023 21:40:01
QuoteSupposing we only run GC between the scripts, during the game frame update

Would the GC not need to happen on each scope change? Or would this only affect the circular dependent object?

What is "scope change"?

GC does not have a strict requirement to happen on particular events, it's okay to run it periodically.
It may be run by a timer, or under some trigger conditions based on managed pool statistics.

It also may be run on a separate thread, but only during some script-unrelated processes, like render or game update, that do not make any immediate script runs.
Title: Re: Supporting pointers in managed structs
Post by: Crimson Wizard on Tue 18/04/2023 22:16:55
Quote from: Crimson Wizard on Tue 18/04/2023 20:27:50If the script memory would have contained hints on the data types in it, we could know where are "dynamic object handles" among it.

In regards to this: as the RTTI feature (https://github.com/adventuregamestudio/ags/blob/ags4/Common/script/cc_reflect.h) is already merged to ags4, it's potentially possible to expand and generate a script global variables table, which would have a structure similar to RTTI tables.

RTTI already has a Field type (https://github.com/adventuregamestudio/ags/blob/31aff8cf6502fd05e61f0f8b04bccfc0e944e8e5/Common/script/cc_reflect.cpp#L90-L96), which may as well be reused here.
It may contain even variable names, but that's not essential, and may be skipped for now.

To summarize, the necessary changes for GC would require:
1) compilers generating a reflection table for script's global variables (each script); the table is saved as another extension to script format, similar to how RTTI table is saved now.
2) writing a search algorithm that finds managed handles in the script memory.
3) figuring out a good trigger condition for running GC.


PS. I'd need to write a proper ticket for this; imo it's better to do this separately, and prior to the task of supporting nested managed pointers.
Title: Re: Supporting pointers in managed structs
Post by: eri0o on Tue 18/04/2023 22:27:49
Not sure if it's because I am doing a lot of python recently, but the first result for garbage collection in Google for me was this: https://devguide.python.org/internals/garbage-collector/

From the link, see

QuoteIn this example, container holds a reference to itself, so even when we remove our reference to it (the variable "container") the reference count never falls to 0 because it still has its own internal reference. Therefore it would never be cleaned just by simple reference counting. For this reason some additional machinery is needed to clean these reference cycles between objects once they become unreachable. This is the cyclic garbage collector, usually called just Garbage Collector (GC), even though reference counting is also a form of garbage collection.
Title: Re: Supporting pointers in managed structs
Post by: Crimson Wizard on Tue 18/04/2023 22:36:09
Quote from: eri0o on Tue 18/04/2023 22:27:49Not sure if it's because I am doing a lot of python recently, but the first result for garbage collection in Google for me was this: https://devguide.python.org/internals/garbage-collector/

Alright, I will read this, maybe this will give ideas how to optimize the whole thing...

Previously I found this funny article about how to write a GC memory in C language:
https://maplant.com/gc.html
Title: Re: Supporting pointers in managed structs
Post by: eri0o on Tue 18/04/2023 22:38:28
If you want to instead play around in a debugger, I found an implementation of it that is not that big

https://github.com/micropython/micropython/blob/master/py/gc.c

I am trying to look if I find some version of this that is even smaller, perhaps for some of those tiny languages available in GH. But this one has a "design document" to compare it seems.

But the most interesting parts of the code above are the comments and the general skeleton of the garbage collector.
Title: Re: Supporting pointers in managed structs
Post by: Crimson Wizard on Tue 18/04/2023 22:55:57
Alright, after reading this article (https://devguide.python.org/internals/garbage-collector/), I can see that Python's gc has an opposite approach, where it does not check which pointers exist in script, instead it checks only the pointers in the "managed objects", to speak in AGS terms.

In a nutshell, there's a separate "reference counter" meant only for GC. It's initialized with real "reference counter" at the start. Then GC scans all the managed objects, and subrefs the objects it references, but it changes the "gc ref count" (not the real one). After that the objects that have 0 ref counts remaining are the ones that do not have link from the script memory.

Then it does a second pass, restoring "gc ref counts", but only starting at the objects that still kept positive "gc ref count" (these are ones that have a link from the script memory).

Finally in the end only the completely unattached objects remain with "gc ref count" = 0.

This is all really clever!, and this is possible to do in AGS without adding anything extra to script format, or checking internal engine refs; because this algorithm does not care about all that, it only cares to find out which refs come from withing the managed objects themselves.
Title: Re: Supporting pointers in managed structs
Post by: eri0o on Wed 19/04/2023 00:15:26
So, rereading your explanation and the article on python garbage collection, I think any actual code reference has to be the actual python code, so if necessary, it's here: https://github.com/python/cpython/blob/main/Modules/gcmodule.c (I mostly like code references to steal variable names...)

But I would say to stick to the concepts you narrowed down in your comment (https://www.adventuregamestudio.co.uk/forums/engine-development/upcoming-experiment-of-supporting-pointers-in-managed-structs/msg636654845/#msg636654845), which I think is a good reference for the AGS version of a GC.

The MicroPython code - lets call it just MP to avoid confusion - is doing something a bit different, which I think is some optimization on top of a Mark and Sweep implementation. A note that like, 20% of issues from MP are Garbage collector related - it seems it matters a lot in embedded systems. The Mark and Sweep in the wikipedia article on GC (https://en.wikipedia.org/wiki/Tracing_garbage_collection), it looks like it's the basic GC to implement - not optimal but reasonably minimal. There is a good visual in the article for it.

(https://upload.wikimedia.org/wikipedia/commons/4/4a/Animation_of_the_Naive_Mark_and_Sweep_Garbage_Collector_Algorithm.gif)
Title: Re: Supporting pointers in managed structs
Post by: Crimson Wizard on Wed 19/04/2023 00:42:00
The "Mark & Sweep" is probably the method which I described earlier, but, from what I understood, it requires to have a "root" reference which AGS does not have. This could be worked around by generating script reflection.

Comparing these two, I'd rather first try the one that does not require this.
Title: Re: Supporting pointers in managed structs
Post by: eri0o on Wed 19/04/2023 01:11:33
I found another text for the same pictures in the Python GC article here: https://csrgxtu.github.io/2020/02/18/CPython-s-Garbage-Collector/

Reading this shorter version I could understand it better how it works. (there is also a for dummies video here (https://www.youtube.com/watch?v=F6u5rhUQ6dU), but it doesn't go into specifics)

Spoiler
thinking about the concept of generations Python GC implements, for optimization, if we want to do that at some point, it could be like, a collection that runs per frame and the old generation is collected in room transitions.

thinking about the basic of the garbage collection, I wonder if it needs the managed pool, the rtti for cross-object references, but not the object themselves for it to work, meaning it can be implemented reasonably separated - which I guess is what you meant when you said the same thing.

Design wise, maybe the GC holds a reference to both things. And I was thinking, that, in the text, when it's designing the memory layout, it mentions that because it's a better performance to do that way instead of the GC holding a map that would contain the objects and their extra properties (gc_ref), and also because this avoids the need to keep things in sync.
[close]

Quote from: Crimson Wizard on Wed 19/04/2023 00:42:00Comparing these two, I'd rather first try the one that does not require this.

Agreed.
Title: Re: Supporting pointers in managed structs
Post by: Crimson Wizard on Thu 20/04/2023 01:24:02
Alright, I wrote something, based on Python's idea:
https://github.com/adventuregamestudio/ags/pull/1923#issuecomment-1515510131
Test build:
https://cirrus-ci.com/task/6681544534786048

It seems working using the simple script test:
Spoiler
Code (ags) Select
managed struct ListItem
{
    ListItem* prev;
    ListItem* next;
   
    String name;
    int data;
};

managed struct LinkedList
{
    ListItem* first;
    ListItem* last;
};

import ListItem* newItem(ListItem* add_after, String name, int data);
import void      detachItem(ListItem* item);
import ListItem* findNthItem(ListItem* first, int n);
import void      printItems(ListItem* first, ListBox* lb);

import ListItem* addItem(this LinkedList*, String name, int data);
import ListItem* remItem(this LinkedList*, ListItem* item);
import ListItem* remItemN(this LinkedList*, int index);
import void      clear(this LinkedList*);

Code (ags) Select

ListItem* newItem(ListItem* add_after, String name, int data)
{
    ListItem* item = new ListItem;
    item.name = name;
    item.data = data;
    if (add_after != null)
    {
        ListItem* next = add_after.next;
        add_after.next = item;
        item.prev = add_after;
        if (next != null)
        {
            next.prev = item;
            item.next = next;
        }
    }
    return item;
}

void detachItem(ListItem* item)
{
    if (item.prev != null)
        item.prev.next = item.next;
    if (item.next != null)
        item.next.prev = item.prev;
    item.next = null;
    item.prev = null;
}

ListItem* findNthItem(ListItem* first, int n)
{
    ListItem* item = first;
    for (int i = 0; i < n; i++)
    {
        if (item.next == null)
            return null;
        item = item.next;
    }
    return item;
}

void printItems(ListItem* first, ListBox* lb)
{
    lb.Clear();
    if (first == null)
        return;
    ListItem* item = first;
    do
    {
        lb.AddItem(String.Format("%s : %d", item.name, item.data));
        item = item.next;
    }
    while (item != null);
}

ListItem* addItem(this LinkedList*, String name, int data)
{
    ListItem* item = newItem(this.last, TextBox1.Text, Random(32000));
    if (this.first == null)
        this.first = item;
    this.last = item;
    return item;
}

ListItem* remItem(this LinkedList*, ListItem* item)
{
    if (item == this.first)
        this.first = item.next;
    if (item == this.last)
        this.last = item.prev;
    detachItem(item);
    return item;
}

ListItem* remItemN(this LinkedList*, int index)
{
    ListItem* item = findNthItem(this.first, ListBox1.SelectedIndex);
    if (item == null)
        return null;
    this.remItem(item);
    return item;
}

void clear(this LinkedList*)
{
    this.first = null;
    this.last = null;
}
[close]
Title: Re: Supporting pointers in managed structs
Post by: eri0o on Sun 23/04/2023 23:13:05
Hey, after applying this, would pursuing a refactor to port the script rewrite idea from Nick's branch much harder or the same difficulty? Because if it doesn't change much, perhaps applying it and later figuring out how to recover some performance by applying that approach would be a way to go.
Title: Re: Supporting pointers in managed structs
Post by: Crimson Wizard on Sun 23/04/2023 23:29:10
The updated version, with some fixes related to game saves, and plugins which create their own objects:
https://cirrus-ci.com/task/5586565305466880
I made a comment about performance:
https://github.com/adventuregamestudio/ags/pull/1923#issuecomment-1519166283

In summary, it reduces slightly, but for me it was only noticeable in script-heavy games when running in "unlimited fps" mode. But I have not tested anything "heavy" that would have objects (cross-)referencing objects, simply because I don't have a ready game with that.

Still, some things in this branch were done as a quick first attempt, and there's room for improvement. Some optimization may be done quicker, other would require a redesign of e.g. managed object storage internally.



Quote from: eri0o on Sun 23/04/2023 23:13:05Hey, after applying this, would pursuing a refactor to port the script rewrite idea from Nick's branch much harder or the same difficulty?

No, I do not think that it makes it more difficult, as the main principles of the script executor's and managed pool's work were not changed.
The biggest changes are related to
* generating extra data (rtti) as a post-step in script compiling;
* loading extra data (rtti), and generating helper data from it;
* disposing user structs and dynamic arrays (changes are contained within Dispose method);
* garbage collector was practically correctly written now.
Title: Re: Supporting pointers in managed structs
Post by: Crimson Wizard on Wed 03/05/2023 15:41:29
Updated version, built on top of the script performance (https://www.adventuregamestudio.co.uk/forums/engine-development/why-isn-t-ags-script-much-much-faster/msg636655108/#msg636655108) fixes:
https://cirrus-ci.com/task/6166331867791360
New related comment on performance:
https://github.com/adventuregamestudio/ags/pull/1923#issuecomment-1532781830
Title: Re: Supporting pointers in managed structs
Post by: Crimson Wizard on Thu 11/05/2023 18:45:46
Well, I've been waiting for anybody interested in testing this, but it's been too long, so I cancelled the feature merged this into ags4 branch.