www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Object cleanup and weak references

reply Max Bolingbroke <"batterseapower{no" sp4/\\/\\}hotmail.com> writes:
Hi,

I was writing a thin wrapper for SDL in D, in which I hoped to do 
something like the following (very much simplified from real code):

static this()
{ SDL_Init(INIT_EVERYTHING); }

static ~this()
{ SDL_Quit(); }

class Surface()
{
	SDL_Surface* handle;

	public this(char[] filename)
	{ handle = IMG_Load(filename); }

	~this()
	{ SDL_FreeSurface(handle); }
}

It looks great and I don't have to remember to explicitly free my 
surfaces. However, there are two problems with Ds shutdown process that 
mean this won't work:

1) In my case the static constructor / destructors were located in a 
separate "initialize" module, and it turned out that on quit it was 
being destructed (running SDL_Quit) BEFORE the GC ran and destructed my 
Surface objects (running SDL_FreeSurface). Clearly we can't Free after 
we have Quit, so that's problem number 1.

2) Even if I removed the call to SDL_Quit it would still fail with an 
access violation on SDL_FreeSurface because D has somehow caused it to 
go away at the point the destructor was run (Anyone know why? I'm using 
Derelict for the SDL imports, by the way).

So, I searched the site and found this guy 
(http://www.digitalmars.com/d/archives/digitalmars/D/learn/729.html) who 
was trying to do >exactly< the same thing as me, and had exactly the 
same problem. I have adopted his solution (an associative array of 
references that is added to when a Surface is created and systematically 
disposed in the static destructor before SDL_Quit occurs) and it works 
nicely, but it means that we have the problem of memory bloat: no 
Surface can ever be GCed before the program terminates because the array 
of surfaces to kill off holds a reference to every single one!

The perfect solution to this is weak references, but I've done some more 
searching and found some posts from a few years back saying that 
apparently D does not support these wonderful things!

So, can anyone tell me if this is still true? And if it is, is there 
another, cleaner, solution to my problem?

Thanks for any help, and sorry about the long post!

Max
Oct 28 2006
next sibling parent reply Bill Baxter <wbaxter gmail.com> writes:
Maybe this message from a few days ago helps?
http://www.digitalmars.com/d/archives/digitalmars/D/learn/5016.html
It may require wrapping your SDL_Surfaces in wrapper objects, becuase 
the above doesn't work with structs.

Something like:

class CSDL_Surface
{
   this(SDL_Surface *s) { surf = s; }
   ~this() { SDL_FreeSurface(surf); }
   SDL_Surface *surf;
}
Then you'll need to always use the class destructor to free surfaces too 
to maintain 1-1 correspondence between live surfaces and objects.

Thomas is probably the only one who can say if it'll work or not, though.

--bb

Max Bolingbroke wrote:
 Hi,
 
 I was writing a thin wrapper for SDL in D, in which I hoped to do 
 something like the following (very much simplified from real code):
 
 static this()
 { SDL_Init(INIT_EVERYTHING); }
 
 static ~this()
 { SDL_Quit(); }
 
 class Surface()
 {
     SDL_Surface* handle;
 
     public this(char[] filename)
     { handle = IMG_Load(filename); }
 
     ~this()
     { SDL_FreeSurface(handle); }
 }
 
 It looks great and I don't have to remember to explicitly free my 
 surfaces. However, there are two problems with Ds shutdown process that 
 mean this won't work:
 
 1) In my case the static constructor / destructors were located in a 
 separate "initialize" module, and it turned out that on quit it was 
 being destructed (running SDL_Quit) BEFORE the GC ran and destructed my 
 Surface objects (running SDL_FreeSurface). Clearly we can't Free after 
 we have Quit, so that's problem number 1.
 
 2) Even if I removed the call to SDL_Quit it would still fail with an 
 access violation on SDL_FreeSurface because D has somehow caused it to 
 go away at the point the destructor was run (Anyone know why? I'm using 
 Derelict for the SDL imports, by the way).
 
 So, I searched the site and found this guy 
 (http://www.digitalmars.com/d/archives/digitalmars/D/learn/729.html) who 
 was trying to do >exactly< the same thing as me, and had exactly the 
 same problem. I have adopted his solution (an associative array of 
 references that is added to when a Surface is created and systematically 
 disposed in the static destructor before SDL_Quit occurs) and it works 
 nicely, but it means that we have the problem of memory bloat: no 
 Surface can ever be GCed before the program terminates because the array 
 of surfaces to kill off holds a reference to every single one!
 
 The perfect solution to this is weak references, but I've done some more 
 searching and found some posts from a few years back saying that 
 apparently D does not support these wonderful things!
 
 So, can anyone tell me if this is still true? And if it is, is there 
 another, cleaner, solution to my problem?
 
 Thanks for any help, and sorry about the long post!
 
 Max

Oct 28 2006
parent reply Max Bolingbroke <"batterseapower{no" sp4/\\/\\}hotmail.com> writes:
Bill Baxter wrote:
 Maybe this message from a few days ago helps?
 http://www.digitalmars.com/d/archives/digitalmars/D/learn/5016.html
 It may require wrapping your SDL_Surfaces in wrapper objects, becuase 
 the above doesn't work with structs.
 
 Something like:
 
 class CSDL_Surface
 {
   this(SDL_Surface *s) { surf = s; }
   ~this() { SDL_FreeSurface(surf); }
   SDL_Surface *surf;
 }
 Then you'll need to always use the class destructor to free surfaces too 
 to maintain 1-1 correspondence between live surfaces and objects.
 
 Thomas is probably the only one who can say if it'll work or not, though.

Wow, I have no idea what that code is doing noodling around in Ds internals, but if it works I will be a very happy man. Unfortunately, my project fails to compile after adding this code: F:\Programming\Current\Destroyed>bud main -cleanup -debug -g -I.\..\;D:\D\dmd\src\ext -of.\bin\Destroyed.exe -w D:\D\dmd\src\phobos\internal\gc\gcx.d(42): module win32 cannot read file 'win32.d' Although it seems that win32.d lives in the same directory as gcx.d... can someone point out what newbie mistake am I making? :-) Thanks a lot for your help Bill! Max
Oct 28 2006
next sibling parent Max Bolingbroke <"batterseapower{no" sp4/\\/\\}hotmail.com> writes:
Max Bolingbroke wrote:
  > Wow, I have no idea what that code is doing noodling around in Ds
 internals, but if it works I will be a very happy man. Unfortunately, my 
 project fails to compile after adding this code:
 
 F:\Programming\Current\Destroyed>bud main -cleanup -debug -g 
 -I.\..\;D:\D\dmd\src\ext -of.\bin\Destroyed.exe -w
 D:\D\dmd\src\phobos\internal\gc\gcx.d(42): module win32 cannot read file 
 'win32.d'
 
 Although it seems that win32.d lives in the same directory as gcx.d... 
 can someone point out what newbie mistake am I making? :-)
 
 Thanks a lot for your help Bill!
 
 Max

Well, I solved the issue just by blindly adding -ID:\D\dmd\src\phobos\internal\gc\ to my DMD options: I have no idea why this would be required, but it works a treat now! I owe a debt of gratitude to Thomas for his awesome code :-). Max
Oct 29 2006
prev sibling parent reply Thomas Kuehne <thomas-dloop kuehne.cn> writes:
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Max Bolingbroke schrieb am 2006-10-29:
 Wow, I have no idea what that code is doing noodling around in Ds 
 internals, but if it works I will be a very happy man. Unfortunately, my 
 project fails to compile after adding this code:

 F:\Programming\Current\Destroyed>bud main -cleanup -debug -g 
 -I.\..\;D:\D\dmd\src\ext -of.\bin\Destroyed.exe -w
 D:\D\dmd\src\phobos\internal\gc\gcx.d(42): module win32 cannot read file 
 'win32.d'

 Although it seems that win32.d lives in the same directory as gcx.d... 
 can someone point out what newbie mistake am I making? :-)

add import internal.gc.win32; You might have to try different import sequences. Thomas -----BEGIN PGP SIGNATURE----- iD8DBQFFRI2kLK5blCcjpWoRAh8eAJ9P3OoJHnQQbTUh4xlUq3ml9W5R2wCcDEY2 HXZYcGeBjnuydlYnD9m1mWo= =VDPQ -----END PGP SIGNATURE-----
Oct 29 2006
parent Max Bolingbroke <"batterseapower{no" sp4/\\/\\}hotmail.com> writes:
Thomas Kuehne wrote:
 Max Bolingbroke schrieb am 2006-10-29:
 Wow, I have no idea what that code is doing noodling around in Ds 
 internals, but if it works I will be a very happy man. Unfortunately, my 
 project fails to compile after adding this code:

 F:\Programming\Current\Destroyed>bud main -cleanup -debug -g 
 -I.\..\;D:\D\dmd\src\ext -of.\bin\Destroyed.exe -w
 D:\D\dmd\src\phobos\internal\gc\gcx.d(42): module win32 cannot read file 
 'win32.d'

 Although it seems that win32.d lives in the same directory as gcx.d... 
 can someone point out what newbie mistake am I making? :-)

add import internal.gc.win32; You might have to try different import sequences.

Thanks Thomas, it works: I assume this is just to force bud to recognize that that file needs to be compiled in? Bit strange that it doesn't pick it up automagically, anyhow.. Thanks again for your help and awesome code :-) Max
Oct 29 2006
prev sibling parent "Lionello Lunesu" <lionello lunesu.remove.com> writes:
"Max Bolingbroke" <"batterseapower{no" sp4/\\/\\}hotmail.com> wrote in 
message news:ei0dnr$lv$1 digitaldaemon.com...
 Hi,

 I was writing a thin wrapper for SDL in D, in which I hoped to do 
 something like the following (very much simplified from real code):

 static this()
 { SDL_Init(INIT_EVERYTHING); }

 static ~this()
 { SDL_Quit(); }

 class Surface()
 {
 SDL_Surface* handle;

 public this(char[] filename)
 { handle = IMG_Load(filename); }

 ~this()
 { SDL_FreeSurface(handle); }
 }

 It looks great and I don't have to remember to explicitly free my 
 surfaces. However, there are two problems with Ds shutdown process that 
 mean this won't work:

 1) In my case the static constructor / destructors were located in a 
 separate "initialize" module, and it turned out that on quit it was being 
 destructed (running SDL_Quit) BEFORE the GC ran and destructed my Surface 
 objects (running SDL_FreeSurface). Clearly we can't Free after we have 
 Quit, so that's problem number 1.

You can force the GC to run a collect: std.gc.fullCollect(); Try doing that in the static ~this. Of course, if there are any pointers (or values looking like pointers!) in memory pointing to a surface object, that object would still not get deleted, causing an exception. L.
Oct 29 2006