FAQFAQ   SearchSearch   MemberlistMemberlist   UsergroupsUsergroups   RegisterRegister 
 ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

Derelict design question

 
Post new topic   Reply to topic     Forum Index -> Derelict
View previous topic :: View next topic  
Author Message
Silverclaw



Joined: 11 Jun 2008
Posts: 5

PostPosted: Fri Feb 05, 2010 7:03 am    Post subject: Derelict design question Reply with quote

I've been coding D and I used Derelict for a few years already on my projects, specially the GL and SDL packages. I also have suicidal tendencies and as such am using DMD2.

Theres been a while since I've coded GL and now that i'm getting back to it, i came here to get an updated version of the bindings. Since D2 introduces TLS I've read the related forum topics regarding this issue and peeked through the code on derelict. While doing it, i came across some of the developer's design decisions I didnt understand.

Firstly, the enourmous ammounts of mixins. True, they are all beign used correctly, but there some that I don't understand why they are there. For example, consider the next code excert from compat.d:

version(D_Version2)
{
mixin("alias const(char)* CCPTR;");
mixin("alias const(wchar)* CWCPTR;");
mixin("alias const(dchar)* CDCPTR;");
mixin("alias const(ubyte)* CUBPTR;");
mixin("alias const(void)* CVPTR;");
}

I don't understand why "mixin" is necessary here. This code should be equivalent to

version(D_Version2)
{
alias const(char)* CCPTR;
alias const(wchar)* CWCPTR;
alias const(dchar)* CDCPTR;
alias const(ubyte)* CUBPTR;
alias const(void)* CVPTR;
}

Obviously, I must've missed something.


Next, usage of version'ed functions is used accross the packages, however, other functions that should be aliased, are not. Consider:

string stripWhiteSpace(string str)
{
version(Tango)
{
return trim(str);
}
else
{
return strip(str);
}
}

Why not

version(Tango)
{
alias trim stripWhiteSpace;
}
else
{
alias strip stripWhiteSpace;
}

Which has the advantage of not created a pass-function to the linker. Granted, any decent compiler will optimize it away, but the solution used is a waste of the language's resources.


The loader is currently a global reference to an object that is constructed on the static this(). While this is ok, the reference itself is not shared accross threads (its set on TLS). Consider the declaration at line 883 in gl.d:

DerelictGLLoader DerelictGL;

My question is, why not implement DerelictGLLoader as a singleton? Its own private reference could then be set to a final shared reference (which doesnt require synchronization since i can't be modified) created on the private this(). To initialize on the static this() one would only need "new DerelictGLLoader");


Lastly, the abstract class SharedLibLoader recieves 3 parameters on its contructor, one for each platform that can be linked with the current version. I don't see the reasoning behind this. The version() statements should be moved to the inherited classes and only 1 parameter should be recieved by the abstract class. My rationale is that if one would want to add another architecture, the base class would require a new argument on its list, requiring all other classes to be modified accordingly. The opposite is also problematic. If one would want to remove the link libraries for a platform the loader would be given a null string for that platform. Moving the version statements to the base classes would allow the developers to add static assert(false, "platform not supported");


Sorry for the long winded post. I'm sure you have good reasons to have designed it the way you did. But if I am to make any patches for the libraries, I would need to know why it was designed the way it did. I do not wish to break any traditions.
Back to top
View user's profile Send private message
doob



Joined: 06 Jan 2007
Posts: 367

PostPosted: Fri Feb 05, 2010 10:06 am    Post subject: Re: Derelict design question Reply with quote

I can answer one of your questions.

Silverclaw wrote:
Firstly, the enourmous ammounts of mixins. True, they are all beign used correctly, but there some that I don't understand why they are there. For example, consider the next code excert from compat.d:

version(D_Version2)
{
mixin("alias const(char)* CCPTR;");
mixin("alias const(wchar)* CWCPTR;");
mixin("alias const(dchar)* CDCPTR;");
mixin("alias const(ubyte)* CUBPTR;");
mixin("alias const(void)* CVPTR;");
}

I don't understand why "mixin" is necessary here. This code should be equivalent to

version(D_Version2)
{
alias const(char)* CCPTR;
alias const(wchar)* CWCPTR;
alias const(dchar)* CDCPTR;
alias const(ubyte)* CUBPTR;
alias const(void)* CVPTR;
}

Obviously, I must've missed something.


The problem with version statements are that everything in them needs to be syntactically correct and "const(void)*" is not syntactically correct D1 code. That almost defeats the whole idea with version(D_Version2). See http://www.digitalmars.com/d/1.0/version.html.
Back to top
View user's profile Send private message
Silverclaw



Joined: 11 Jun 2008
Posts: 5

PostPosted: Fri Feb 05, 2010 11:16 am    Post subject: Re: Derelict design question Reply with quote

doob wrote:

The problem with version statements are that everything in them needs to be syntactically correct and "const(void)*" is not syntactically correct D1 code. That almost defeats the whole idea with version(D_Version2). See http://www.digitalmars.com/d/1.0/version.html.


I see. And I agree, it defeats the purpose of version statements. Alas, its the specc, we can't fight it. Thanks for the clarification.

There might be a clearer way around it. Experimentation on the way!
Back to top
View user's profile Send private message
doob



Joined: 06 Jan 2007
Posts: 367

PostPosted: Fri Feb 05, 2010 3:00 pm    Post subject: Re: Derelict design question Reply with quote

Silverclaw wrote:
There might be a clearer way around it. Experimentation on the way!

I doubt that there is another way around it as things look now. The compiler will first parse the whole file before removing the inactive version statements and "const(char)*" is simple not syntactically valid D1 code. It will choke on that before it has a chance to remove the version statement.
Back to top
View user's profile Send private message
Stanley Pancakes



Joined: 26 Dec 2009
Posts: 18

PostPosted: Sat Feb 06, 2010 12:53 am    Post subject: Reply with quote

Well, there is 'another' solution if it may be considered as such:

Code:


template ConstType(T)
{
version(D_Version2)
{
    mixin("alias const(T) ConstType;");
}
else
{
    alias T ConstType;
}
}



But that would actually only reduce the number of 'version' statements and make code a bit shorter, though it essentially uses the same mixin concept. (And then again, don't forget that aldacron doesn't like templates Laughing )

The 'version' statements indeed lack some finesse, though I actually find absence of operator ! even more annoying than the demand of syntactically correct code.
Back to top
View user's profile Send private message
doob



Joined: 06 Jan 2007
Posts: 367

PostPosted: Sat Feb 06, 2010 4:20 am    Post subject: Reply with quote

Stanley Pancakes wrote:

But that would actually only reduce the number of 'version' statements and make code a bit shorter, though it essentially uses the same mixin concept. (And then again, don't forget that aldacron doesn't like templates Laughing ).

Yes exactly, but I think his biggest concern was the increase of compile time and as we have show it's better to have few string mixins that mixes in a lot of code compared to many string mixins with a small amount of code.
Back to top
View user's profile Send private message
aldacron



Joined: 05 May 2004
Posts: 1322
Location: Seoul, South Korea

PostPosted: Sat Feb 06, 2010 9:32 am    Post subject: Re: Derelict design question Reply with quote

Silverclaw wrote:

Next, usage of version'ed functions is used accross the packages, however, other functions that should be aliased, are not. Consider:

string stripWhiteSpace(string str)
{
version(Tango)
{
return trim(str);
}
else
{
return strip(str);
}
}

Why not

version(Tango)
{
alias trim stripWhiteSpace;
}
else
{
alias strip stripWhiteSpace;
}

Which has the advantage of not created a pass-function to the linker. Granted, any decent compiler will optimize it away, but the solution used is a waste of the language's resources.


Honestly, it never really occurred to me. I don't mind aliasing types, but I typically prefer not to alias functions at global scope.

Quote:

The loader is currently a global reference to an object that is constructed on the static this(). While this is ok, the reference itself is not shared accross threads (its set on TLS). Consider the declaration at line 883 in gl.d:

DerelictGLLoader DerelictGL;

My question is, why not implement DerelictGLLoader as a singleton? Its own private reference could then be set to a final shared reference (which doesnt require synchronization since i can't be modified) created on the private this(). To initialize on the static this() one would only need "new DerelictGLLoader");


I had not even considered issues with the shared paradigm until they were brought up in the thread you reference. I haven't yet decided what to do about the global loaders. I haven't experimented with shared/__gshared at all, and I'm not entirely clear on the interactions. It's something I've put off until later.

This is one approach to consider, but I'm open to alternatives as well.

Quote:

Lastly, the abstract class SharedLibLoader recieves 3 parameters on its contructor, one for each platform that can be linked with the current version. I don't see the reasoning behind this. The version() statements should be moved to the inherited classes and only 1 parameter should be recieved by the abstract class. My rationale is that if one would want to add another architecture, the base class would require a new argument on its list, requiring all other classes to be modified accordingly. The opposite is also problematic. If one would want to remove the link libraries for a platform the loader would be given a null string for that platform. Moving the version statements to the base classes would allow the developers to add static assert(false, "platform not supported");


This is just an artifact from the original loader that's been carried over across two rewrites for no particular reason. If you want to submit a patch to change it, I'll put it in.
_________________
The One With D | The One With Aldacron | D Bits
Back to top
View user's profile Send private message Send e-mail
Silverclaw



Joined: 11 Jun 2008
Posts: 5

PostPosted: Sun Feb 07, 2010 6:56 am    Post subject: Re: Derelict design question Reply with quote

aldacron wrote:

[snip] I typically prefer not to alias functions at global scope.


May I ask why? (even if it is a personal preference, I'll accept that)

aldacron wrote:

I had not even considered issues with the shared paradigm until they were brought up in the thread you reference. I haven't yet decided what to do about the global loaders. I haven't experimented with shared/__gshared at all, and I'm not entirely clear on the interactions. It's something I've put off until later.

This is one approach to consider, but I'm open to alternatives as well.


I understand. And to be honest, i think that changing the code now may end up being wasted effort, as the threading model for D isn't finished yet. We might as well wait for it before effecting any changes in this regard.

aldacron wrote:

This is just an artifact from the original loader that's been carried over across two rewrites for no particular reason. If you want to submit a patch to change it, I'll put it in.


Here it is: http://dsource.org/projects/derelict/ticket/48
Hope it is satisfactory.
Back to top
View user's profile Send private message
aldacron



Joined: 05 May 2004
Posts: 1322
Location: Seoul, South Korea

PostPosted: Sun Feb 07, 2010 9:02 am    Post subject: Re: Derelict design question Reply with quote

Silverclaw wrote:

May I ask why? (even if it is a personal preference, I'll accept that)


Personal preference is all it boils down to, really. Plus, if there's a change in a future version of Tango or Phobos (one of the functions gets an extra parameter, or a name change), it will be obvious what the error is since it only happens in one place. Just imagine someone downloading the trunk, compiling, and finding errors all over the place naming a function that doesn't appear at point the error occurs (aliases aren't visible in error text, you see what they refer to instead).

Quote:

Here it is: http://dsource.org/projects/derelict/ticket/48
Hope it is satisfactory.


Thanks! One thing to point out for future patches. For the Derelict2 branch there is no need to worry about backwards compatibility with third-party packages. The branch is considered to be in an alpha state and should be expected to break things. I typically don't worry about that anyway. Changes to the loader are rare, but when they do occur I expect the package maintainers, or one of the users, will do what they need to do to keep up.
_________________
The One With D | The One With Aldacron | D Bits
Back to top
View user's profile Send private message Send e-mail
aldacron



Joined: 05 May 2004
Posts: 1322
Location: Seoul, South Korea

PostPosted: Sun Feb 07, 2010 9:39 am    Post subject: Re: Derelict design question Reply with quote

Silverclaw wrote:

Here it is: http://dsource.org/projects/derelict/ticket/48
Hope it is satisfactory.


OK, now I remember why we (can't remember if it was me or h3r3tic) passed multiple strings to the loader in the first place. Handling all the different versions in each module is a nightmare. And quite ugly. Consider that there's darwin and OSX for Mac. freebsd uses (generally) the same lib names as Linux. The same could be said for solaris, which we don't support yet, but I hope to one day.

Using your approach, that means this sort of thing has to go into each package loader implementation:

Code:

this()
{
    version(Windows) super("foo");
    version(linux) super("bar");
    version(darwin) super("blarg");
    version(OSX) super("blarg");
    version(freebsd) super("bar");
    version(solaris) super("bar");
}


Or, alternatively:

Code:

// at the top of the module
version(darwin) version = Mac;
version(OSX) version = Mac;
version(linux) version = Nix;
version(solaris) version = Nix;
version(freebsd) version = Nix;

// then, in the constructor
version(Windows) super("foo");
version(Nix) super("bar");
version(Mac) super("blarg");


Either way, it's a bit of a mess. Passing multiple strings to the super constructor, even though some of them are wasted, keeps the messiness out of the packages and in one, central location. And if we one day to need to add an extra parameter, say for solaris, then it wouldn't be any more effort than using the approach you suggest.

Sorry I didn't remember this sooner. It didn't hit me until I sat down to apply your patch.
_________________
The One With D | The One With Aldacron | D Bits
Back to top
View user's profile Send private message Send e-mail
Silverclaw



Joined: 11 Jun 2008
Posts: 5

PostPosted: Sun Feb 07, 2010 10:13 am    Post subject: Reply with quote

I understand the reasoning, alas I simply wanted to reduce the number of string literals the compiler has to handle (and therefore less manifest constants), less stack allocations due to parameter passing (which as I said before, compilers *should* be smart enough to remove).

Although, since both loaders aren't mutually exclusive, would it be possible to keep at least the single-string constructor of SharedLoader?


On a side note, there is little documentation spread on the source. Is this on purpose? (ie, is the source documentation found elsewhere?) If not, I might add a doc comment here and there as I've lost my way around the code a couple of times already.
Back to top
View user's profile Send private message
aldacron



Joined: 05 May 2004
Posts: 1322
Location: Seoul, South Korea

PostPosted: Sun Feb 07, 2010 10:37 pm    Post subject: Reply with quote

Silverclaw wrote:
I understand the reasoning, alas I simply wanted to reduce the number of string literals the compiler has to handle (and therefore less manifest constants), less stack allocations due to parameter passing (which as I said before, compilers *should* be smart enough to remove).


Yeah, the overhead here is really not a big deal in this case. Granted, though, I would like to take the time one day and figure out something a bit cleaner.

Quote:

On a side note, there is little documentation spread on the source. Is this on purpose? (ie, is the source documentation found elsewhere?) If not, I might add a doc comment here and there as I've lost my way around the code a couple of times already.


Right, no source documentation at all. I never saw the need for it in the past because things were quite simple and there weren't many people mucking about with the internals. Given that I can't even remember why certain decisions were made, there will certainly be a number of comments by the time of release, particularly in DerelictUtil. The other packages will be commented to highlight certain differences between the binding and the original C APIs.
_________________
The One With D | The One With Aldacron | D Bits
Back to top
View user's profile Send private message Send e-mail
Display posts from previous:   
Post new topic   Reply to topic     Forum Index -> Derelict All times are GMT - 6 Hours
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum


Powered by phpBB © 2001, 2005 phpBB Group