Moving the string to the global pool is necessary, because if the string was local, the GC would delete it before the code is resumed. However, this causes issues when the same string is used:
new String:str = str_new_static("string");
Func(str, 1000);
Func(str, 1500);
Since both functions are called at the same time, by the time the second one is resumed, the string has been already collected, because during the wait, it is moved back to the local pool.
This problem cannot be solved without storing additional information about the string, but this makes the code more complicated.
The proposal
I propose a breaking change to how the global pool works. Instead of simple operations like to_local and to_global, new operations would be introduced: use and release. The functions would manipulate the internal "reference count" every GC-object has. When the count is increased from 0, the object would be moved to the global pool. When the count is decreased from 1, the object would be moved to the local pool. Decreasing the count from 0 would produce a warning.
The GC will behave in the same way: objects in the local pool will be collected when the current supercontext is released (i.e. at the end of the top-level callback).
Together with this change, the global versions of the tags will be removed as well, since every individual function can handle the ownership of the string on its own, if it needs to. As a result, deleting a string will be always almost unneeded, because if the counts of use and release are balanced, the GC will automatically delete it eventually.
Guards
Guards would be modified to use the use and release operations by default, instead of delete. Thanks to this, the function above could be rewritten like this:
pawn_guard will call use on str when used, increasing the count by 1. Since the guard will remain alive during the whole forked context, the string cannot be collected. Once the context ands, the guard will be destroyed and as a result, release is called on str. If nothing else owns the string, it will be collected.
Collections
At the moment, storing dynamic strings in collections is simple: move it to the global pool and add it to the collection. Calling list_delete_deep and similar functions will destroy the string stored inside.
I think list_delete_deep should instead call release on the string, decreasing the reference count. This makes sharing the same string in multiple containers very easy, since you only need to call str_use (as you would have used str_to_global previously) before storing the string in a container, and list_delete_deep would make it be deleted eventually.
The only slight issue is with variant references stored inside containers. Previously, the function would have called var_delete_deep on the variant (recursively deleting its contents), but since it should behave the same way for any GC-object, it would call release on it, and the GC will then delete the variant without deleting its contents.
Since variants should be only used for passing dynamic values around, I guess this is not such a large issue for most users. In addition, there might be another type of GC-object added in the future that would solve this.
The impact
GlobalString, GlobalVariant, and GlobalIter tags are removed.
*_to_global functions removed, *_use is introduced.
*_to_local functions removed, *_release is introduced.
tag_op_delete_deep (free in C++) renamed to tag_op_release. Retains its effect for non-GC-collected objects, but decreases the reference count for GC-objects.
tag_op_use introduced. Increases the reference count for GC-objects. Does nothing for other objects.
var_delete_deep removed (to reduce confusion and enforce proper usage). Variant references stored in collections will not be recursively deleted when the collection is recursively deleted.
Since this is quite a significant change to how a part of this plugin works (albeit one I consider beneficial), I have decided to first introduce it in this manner, for others to discuss and add their own insights.
The issue
Currently, functions like
str_to_global
andstr_to_local
simply move the string between the two pools. However, let's have a situation like this:Moving the string to the global pool is necessary, because if the string was local, the GC would delete it before the code is resumed. However, this causes issues when the same string is used:
Since both functions are called at the same time, by the time the second one is resumed, the string has been already collected, because during the wait, it is moved back to the local pool.
This problem cannot be solved without storing additional information about the string, but this makes the code more complicated.
The proposal
I propose a breaking change to how the global pool works. Instead of simple operations like
to_local
andto_global
, new operations would be introduced:use
andrelease
. The functions would manipulate the internal "reference count" every GC-object has. When the count is increased from 0, the object would be moved to the global pool. When the count is decreased from 1, the object would be moved to the local pool. Decreasing the count from 0 would produce a warning.The GC will behave in the same way: objects in the local pool will be collected when the current supercontext is released (i.e. at the end of the top-level callback).
Together with this change, the global versions of the tags will be removed as well, since every individual function can handle the ownership of the string on its own, if it needs to. As a result, deleting a string will be always almost unneeded, because if the counts of
use
andrelease
are balanced, the GC will automatically delete it eventually.Guards
Guards would be modified to use the
use
andrelease
operations by default, instead ofdelete
. Thanks to this, the function above could be rewritten like this:pawn_guard
will calluse
onstr
when used, increasing the count by 1. Since the guard will remain alive during the whole forked context, the string cannot be collected. Once the context ands, the guard will be destroyed and as a result,release
is called onstr
. If nothing else owns the string, it will be collected.Collections
At the moment, storing dynamic strings in collections is simple: move it to the global pool and add it to the collection. Calling
list_delete_deep
and similar functions will destroy the string stored inside.I think
list_delete_deep
should instead callrelease
on the string, decreasing the reference count. This makes sharing the same string in multiple containers very easy, since you only need to callstr_use
(as you would have usedstr_to_global
previously) before storing the string in a container, andlist_delete_deep
would make it be deleted eventually.The only slight issue is with variant references stored inside containers. Previously, the function would have called
var_delete_deep
on the variant (recursively deleting its contents), but since it should behave the same way for any GC-object, it would callrelease
on it, and the GC will then delete the variant without deleting its contents.Since variants should be only used for passing dynamic values around, I guess this is not such a large issue for most users. In addition, there might be another type of GC-object added in the future that would solve this.
The impact
GlobalString
,GlobalVariant
, andGlobalIter
tags are removed.*_to_global
functions removed,*_use
is introduced.*_to_local
functions removed,*_release
is introduced.tag_op_delete_deep
(free
in C++) renamed totag_op_release
. Retains its effect for non-GC-collected objects, but decreases the reference count for GC-objects.tag_op_use
introduced. Increases the reference count for GC-objects. Does nothing for other objects.var_delete_deep
removed (to reduce confusion and enforce proper usage). Variant references stored in collections will not be recursively deleted when the collection is recursively deleted.Since this is quite a significant change to how a part of this plugin works (albeit one I consider beneficial), I have decided to first introduce it in this manner, for others to discuss and add their own insights.