Jump to page: 1 2 3
Thread overview
SDL* and GL bindings
May 20, 2006
Bruno Medeiros
May 20, 2006
Ant
May 21, 2006
Bruno Medeiros
May 22, 2006
James Dunne
May 21, 2006
Mike Parker
May 21, 2006
Bruno Medeiros
May 21, 2006
Daniel Keep
May 21, 2006
Mike Parker
May 22, 2006
Bruno Medeiros
May 22, 2006
Mike Parker
May 22, 2006
Bruno Medeiros
May 21, 2006
Bruno Medeiros
May 22, 2006
Bruno Medeiros
May 22, 2006
Daniel Keep
May 22, 2006
Bruno Medeiros
May 23, 2006
Derek Parnell
May 23, 2006
Derek Parnell
May 23, 2006
Brad Anderson
May 20, 2006
My slightly ranty topic about the current state of SDL* and GL bindings.

Recently, some development in a program of mine made look for a new SDL+GL binding other than the one I was using. I was a bit dismayed about the current state of affairs, as I couldn't find a suitable binding. The existing bindings (that I know of) are D-Porting, Derelict and Anders's. The problems I found:

Dporting:
This was the version I was previously using (actually a modified version with module statements). The problem is that this binding is not actively developed (I think) and the creators are not accessible, at least not easily (the Japanese D community?). This became an issue because there is no documentation at all and I this I was unable to know how this binding dealt with the SDL_main hack. Similarly how could I suggest or discuss the implementation of proper module names?


Derelict:
I'm really not into Derelict's runtime lib loading. I think the advantages presented by this method (see at Static Import Issues at http://svn.dsource.org/projects/derelict/trunk/docs/index.html ) matter very little. The first issue can be solved by a one person, one time operation, who can then distribute the lib to other people. In fact, for dmd it amounts to "coffimplib <foobar.lib>", which hardly is a nuisance.

As for the second issue, c'mon, what more than displaying an error message can a program do when an /essential/ dll is not found? Indeed, how many games (or any application for that matter) do you know that have a different behavior when a dll is missing than the standard system error message?

Meanwhile, I believe that the disadvantages, such as Derelict being harder/longer to maintain/develop than a regular binding have much more impact. For instance Derelict currently *still* doesn't support MacOS. (Not that I use (or even like) Macs, but it's quite an important platform that should be supported)

This seems a shame to me, since Derelict is clearly the binding with the most commitment, exposure, and work done on it.

Anders's:
I assume the latest bindings are those of http://www.algonet.se/~afb/d/ am I right?
How does one work with the bindings? I don't know if they are supposed to be compiled, or just imported header-style, as no doc/readme explains it. And in any way:
- Compilation fails (at least the easy way) because of the mismatched module names. What's up with that? Why do only sdl.main and sdl.sdl have module statements?
- Importing as headers should work if one doesn't use SDL_InitApplication, or so I thought, but it still fails because of a missing:
  Error 42: Symbol Undefined __init_6events9SDL_Event
caused by the "wchar unicode" of "SDL_keysym" which has a non-zero init. Of course, it took me a while to figure this all out.

-- 
Bruno Medeiros - CS/E student
http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#D
May 20, 2006
Bruno Medeiros wrote:
> My slightly ranty topic about the current state of SDL* and GL bindings.


should I post a reply? should I?

no, better not...

Ant
May 21, 2006
Bruno Medeiros wrote:

> 
> Derelict:
> I'm really not into Derelict's runtime lib loading. I think the advantages presented by this method (see at Static Import Issues at http://svn.dsource.org/projects/derelict/trunk/docs/index.html ) matter very little. The first issue can be solved by a one person, one time operation, who can then distribute the lib to other people. In fact, for dmd it amounts to "coffimplib <foobar.lib>", which hardly is a nuisance.

It's a nuisance to me. I started Derelict for my own use because I wasn't happy with the absence of runtime loading in existing bindings. I just happened to make it available for others to use as a small contribution to the D community. One man's trash is another man's treasure, as they say.


> 
> As for the second issue, c'mon, what more than displaying an error message can a program do when an /essential/ dll is not found? Indeed, how many games (or any application for that matter) do you know that have a different behavior when a dll is missing than the standard system error message?
> 

The problem is that when you consider the Windows environment, which is the platform most commercial games target, the average user is not all that tech savvy. A message that an application can't be launched because a DLL is missing is going to be meaningless to most people. The user will immediately blame the developer for releasing a 'faulty' program. The chain of consequences from that point on can go in any direction, with the best result being a tech support request and the worst being a lost sale or negative word of mouth. From a AAA, retail, boxed game perspective, no big deal. In the downloadable games space, it's a very big deal. Particularly for casual games developers. That's my primary area of interest.

Being able to control the type of error message displayed gives the developer the ability to provide more detailed information. Popping up a message box with instructions on how to solve the problem, or perhaps with the URL of a web page that contains such instructions, is much more user-friendly. It reduces the chance that the user will go ballistic and makes a much more professional presentation. That's the motivation behind that design decision. It's influenced by Windows, because that's my primary development environment. The Linux interface is the same for consistency.

> Meanwhile, I believe that the disadvantages, such as Derelict being harder/longer to maintain/develop than a regular binding have much more impact. For instance Derelict currently *still* doesn't support MacOS. (Not that I use (or even like) Macs, but it's quite an important platform that should be supported)
> 
> This seems a shame to me, since Derelict is clearly the binding with the most commitment, exposure, and work done on it.


Derelict really isn't difficult or time consuming to maintain. I just don't spend much time on it because I have a lot going on. From the beginning it's been a hobby. Derelict doesn't support Mac because I don't own a Mac. I would dearly love to get Mac support into the trunk. The only reason Linux is supported is because two of the maintainers (John & Tom) and other Derelict users (like Clay) regularly work on Linux. If not for them, Windows would be the only supported platform (I'm not a fan of Linux at all).

Anders is the only one who has ever contributed any Mac code to Derelict. If I could get someone on board who would be willing to maintain the Mac side of things, I would happily get Brad to grant them commit rights and let them at it. Until that day comes, there's nothing I can do about it. I'm not going to go out and buy a Mac just to add Mac support to a project I work on in my spare time.

Along the same vein, I'd also like to make the code base compatible with GDC on all three platforms. I can't be bothered to do it myself. So if anyone would be willing to maintain it, they are welcome to. That's the thing about open source projects. If there's something you see it needs, you can contribute it. Derelict is very much a community project. Many of the packages were contributed by users.

For what it's worth, one of the future additions to Derelict is going to be the ability to link statically with an import library and bypass the runtime loading mechanism. I prefer the runtime loading myself because of the flexibility it gives in runtime swapping of APIs (a lot of games use that technique - Unreal engine games, Quake engine games, the Age of Empires series, and others). But I do understand that, for whatever reason, it's a problem for some people (I still don't understand why - it's all done behind the scenes, there's no impact on performance, and it's highly flexible). I've already incorporated the ability into DerelictAL, though it hasn't been tested at all. To make use of it, just add the line 'version=DerelictAL_Static' to DerelictAL/forbuild.txt. Over the coming months I will add the same functionality to the other packages. No promises as to how long it will ultimately be, though.
May 21, 2006
Bruno Medeiros wrote:

> Anders's:
> I assume the latest bindings are those of http://www.algonet.se/~afb/d/ am I right?

Yeah, although not in the zip files but *only* in the cvs section.

But I just updated the old versions that were provided by DedicateD,
and are also available from Japan: http://shinh.skr.jp/d/porting.html

A recent post included more modern/open source versions of the headers,
built from Mesa OpenGL and FreeGLUT instead of "my" SGI OpenGL and GLUT.

http://www.digitalmars.com/d/archives/digitalmars/D/announce/2982.html

> How does one work with the bindings? I don't know if they are supposed to be compiled, or just imported header-style, as no doc/readme explains it.

The makefiles and sample projects are all in the CVS for now, sorry.

Basically I just include everything in sdl/*.d and link it with SDL ?
(similar to how you use it from C, so it's wasn't very documented no)

> And in any way:
> - Compilation fails (at least the easy way) because of the mismatched module names. What's up with that? Why do only sdl.main and sdl.sdl have module statements?

The other ones just had implicit names, since they're all in "sdl"...

> - Importing as headers should work if one doesn't use SDL_InitApplication, or so I thought, but it still fails because of a missing:
>   Error 42: Symbol Undefined __init_6events9SDL_Event
> caused by the "wchar unicode" of "SDL_keysym" which has a non-zero init. Of course, it took me a while to figure this all out.

It should work with the mentioned Makefile, which builds a D library
(some of the SDL import modules do generate code, it's inevitable)

But when I said that I should really, really package it up better -
this is what I meant. It works for me, but I haven't documented it.


Once I update the headers to SDL 1.2.10 (etc), I will package it up too.
It will probably only work with GDC, but Derelict is good for DMD use ?

I also promised to write a tutorial for it, similar to the ones at:
http://dmedia.dprogramming.com/Main/Tutorials

--anders
May 21, 2006
Bruno Medeiros wrote:

> Meanwhile, I believe that the disadvantages, such as Derelict being harder/longer to maintain/develop than a regular binding have much more impact. For instance Derelict currently *still* doesn't support MacOS. (Not that I use (or even like) Macs, but it's quite an important platform that should be supported)

Derelict doesn't have any technical reasons for not running on Mac OS X, it's just lacking active users to help implement the needed sections...

I posted a few patches / items left to do, on the Derelict forum.
http://www.dsource.org/forums/viewforum.php?f=19

Basically add a few locations to where the libraries are located, and implement "AGL", "NSGL", and "CGL" for the platform OpenGL extensions ?

http://developer.apple.com/qa/qa2001/qa1269.html

The Mac issues were mostly about the needed environment, like setting up
"Build" (for build scripts) and "dlcompat" (for shared library loading)

--anders
May 21, 2006
Anders F Björklund wrote:
> Bruno Medeiros wrote:
> 
>> Anders's:
>> I assume the latest bindings are those of http://www.algonet.se/~afb/d/ am I right?
> 
> Yeah, although not in the zip files but *only* in the cvs section.
> 

Ah, hadn't noticed that.

> But I just updated the old versions that were provided by DedicateD,
> and are also available from Japan: http://shinh.skr.jp/d/porting.html
> 

Huh? I don't understand what you mean here. Where your bindings also based on DedicateD? What what does that have to do with the D-Porting bindings and why did you mention it?

> A recent post included more modern/open source versions of the headers,
> built from Mesa OpenGL and FreeGLUT instead of "my" SGI OpenGL and GLUT.
> 
> http://www.digitalmars.com/d/archives/digitalmars/D/announce/2982.html
> 

*Sigh*, yes, another binding to the mix. Like you said it seems similar to Derelict, except it is based on different GL headers?
I (currently) don't know enough about OpenGL to know the differences (and thus the advantages) between those newer/older opengl headers, so I don't know how more useful that binding would be over Derelict. (Meaning I'd rather use Derelict over that one)

>> How does one work with the bindings? I don't know if they are supposed to be compiled, or just imported header-style, as no doc/readme explains it.
> 
> The makefiles and sample projects are all in the CVS for now, sorry.
> 
> Basically I just include everything in sdl/*.d and link it with SDL ?
> (similar to how you use it from C, so it's wasn't very documented no)
> 

No, you *also* have to link with the SDL_D libs, right? (This is something that must be mentioned.)

>> And in any way:
>> - Compilation fails (at least the easy way) because of the mismatched module names. What's up with that? Why do only sdl.main and sdl.sdl have module statements?
> 
> The other ones just had implicit names, since they're all in "sdl"...
> 

I know they have implicit names, I can see that myself :P
But it is still broke. It works *only* if you compile each module separately, *and* if you only import sdl.sdl (or sdl.main) in your application. (if you import certain other sdl modules it breaks)
Surely you agree this is not proper behavior.?

>> - Importing as headers should work if one doesn't use SDL_InitApplication, or so I thought, but it still fails because of a missing:
>>   Error 42: Symbol Undefined __init_6events9SDL_Event
>> caused by the "wchar unicode" of "SDL_keysym" which has a non-zero init. Of course, it took me a while to figure this all out.
> 
> It should work with the mentioned Makefile, which builds a D library
> (some of the SDL import modules do generate code, it's inevitable)
> 
> But when I said that I should really, really package it up better -
> this is what I meant. It works for me, but I haven't documented it.
> 
> 

The doc needed for this kind of stuff isn't much, just a couple paragraphs on a README file, I think.

> Once I update the headers to SDL 1.2.10 (etc), I will package it up too.
> It will probably only work with GDC, but Derelict is good for DMD use ?
> 
Whoa! "will probably only work with GDC" !?  :|
(And MikeP also mentioned that Derelict didn't work with GDC as of yet) Are GDC and DMD so far off from each other that it is hard to build code that works in both?! What kind of things are working differently? (I only know of 'the lack of newer D features', and 'the typeof ==/is problem', both of which don't seem very problematic to the creation of bindings.)

> I also promised to write a tutorial for it, similar to the ones at:
> http://dmedia.dprogramming.com/Main/Tutorials
> 
> --anders


-- 
Bruno Medeiros - CS/E student
http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#D
May 21, 2006
Mike Parker wrote:
> Bruno Medeiros wrote:
> 
>>
>> Derelict:
>> I'm really not into Derelict's runtime lib loading. I think the advantages presented by this method (see at Static Import Issues at http://svn.dsource.org/projects/derelict/trunk/docs/index.html ) matter very little. The first issue can be solved by a one person, one time operation, who can then distribute the lib to other people. In fact, for dmd it amounts to "coffimplib <foobar.lib>", which hardly is a nuisance.
> 
> It's a nuisance to me. I started Derelict for my own use because I wasn't happy with the absence of runtime loading in existing bindings. I just happened to make it available for others to use as a small contribution to the D community. One man's trash is another man's treasure, as they say.
> 
> 

Yes, I understand it is a nuisance to you. But you wrote about the static linking disadvantages as if it were somewhat universally disadvantageous to anyone. :o (I just wanted to point it wasn't)

>>
>> As for the second issue, c'mon, what more than displaying an error message can a program do when an /essential/ dll is not found? Indeed, how many games (or any application for that matter) do you know that have a different behavior when a dll is missing than the standard system error message?
>>
> 
> The problem is that when you consider the Windows environment, which is the platform most commercial games target, the average user is not all that tech savvy. A message that an application can't be launched because a DLL is missing is going to be meaningless to most people. The user will immediately blame the developer for releasing a 'faulty' program. The chain of consequences from that point on can go in any direction, with the best result being a tech support request and the worst being a lost sale or negative word of mouth. From a AAA, retail, boxed game perspective, no big deal. In the downloadable games space, it's a very big deal. Particularly for casual games developers. That's my primary area of interest.
> 
> Being able to control the type of error message displayed gives the developer the ability to provide more detailed information. Popping up a message box with instructions on how to solve the problem, or perhaps with the URL of a web page that contains such instructions, is much more user-friendly. It reduces the chance that the user will go ballistic and makes a much more professional presentation. That's the motivation behind that design decision. It's influenced by Windows, because that's my primary development environment. The Linux interface is the same for consistency.
> 

But since dll's are usually packaged with the game/app itself, isn't a missing dll something so rare that it is exaggerated to worry about it so much? How can it happen at all, it seems so far-fetched.

>> Meanwhile, I believe that the disadvantages, such as Derelict being harder/longer to maintain/develop than a regular binding have much more impact. For instance Derelict currently *still* doesn't support MacOS. (Not that I use (or even like) Macs, but it's quite an important platform that should be supported)
>>
>> This seems a shame to me, since Derelict is clearly the binding with the most commitment, exposure, and work done on it.
> 
> 
> Derelict really isn't difficult or time consuming to maintain. I just 

It's more work than a pure static-link binding, no?

> don't spend much time on it because I have a lot going on. From the beginning it's been a hobby. Derelict doesn't support Mac because I don't own a Mac. I would dearly love to get Mac support into the trunk. The only reason Linux is supported is because two of the maintainers 

Wouldn't a pure header conversion work in all platforms that the original header works in?...
It doesn't work on Mac, and it didn't work before in Linux because you need to add the code to the dynamic binding, right?

(Note: I'm not demanding or asking that you should work on static-link binding, as I'm fully aware this is your hobby project and you do whatever you want with it. I'm (mostly) just giving my take on the static vs. dynamic binding issue.)

> 
> For what it's worth, one of the future additions to Derelict is going to be the ability to link statically with an import library and bypass the runtime loading mechanism. 

That would be great!

-- 
Bruno Medeiros - CS/E student
http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#D
May 21, 2006
Ant wrote:
> Bruno Medeiros wrote:
>> My slightly ranty topic about the current state of SDL* and GL bindings.
> 
> 
> should I post a reply? should I?
> 
> no, better not...
> 
> Ant

Huh...?
Did I post something wrong or inappropriate?

-- 
Bruno Medeiros - CS/E student
http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#D
May 21, 2006

Bruno Medeiros wrote:
> Mike Parker wrote:
>> Bruno Medeiros wrote:
>>
>>>
>>> Derelict:
>>> I'm really not into Derelict's runtime lib loading. I think the
>>> advantages presented by this method (see at Static Import Issues at
>>> http://svn.dsource.org/projects/derelict/trunk/docs/index.html )
>>> matter very little. The first issue can be solved by a one person,
>>> one time operation, who can then distribute the lib to other people.
>>> In fact, for dmd it amounts to "coffimplib <foobar.lib>", which
>>> hardly is a nuisance.
>>
>> It's a nuisance to me. I started Derelict for my own use because I wasn't happy with the absence of runtime loading in existing bindings. I just happened to make it available for others to use as a small contribution to the D community. One man's trash is another man's treasure, as they say.
>>
>>
> 
> Yes, I understand it is a nuisance to you. But you wrote about the static linking disadvantages as if it were somewhat universally disadvantageous to anyone. :o (I just wanted to point it wasn't)
> 
>>>
>>> As for the second issue, c'mon, what more than displaying an error message can a program do when an /essential/ dll is not found? Indeed, how many games (or any application for that matter) do you know that have a different behavior when a dll is missing than the standard system error message?
>>>
>>
>> The problem is that when you consider the Windows environment, which is the platform most commercial games target, the average user is not all that tech savvy. A message that an application can't be launched because a DLL is missing is going to be meaningless to most people. The user will immediately blame the developer for releasing a 'faulty' program. The chain of consequences from that point on can go in any direction, with the best result being a tech support request and the worst being a lost sale or negative word of mouth. From a AAA, retail, boxed game perspective, no big deal. In the downloadable games space, it's a very big deal. Particularly for casual games developers. That's my primary area of interest.
>>
>> Being able to control the type of error message displayed gives the developer the ability to provide more detailed information. Popping up a message box with instructions on how to solve the problem, or perhaps with the URL of a web page that contains such instructions, is much more user-friendly. It reduces the chance that the user will go ballistic and makes a much more professional presentation. That's the motivation behind that design decision. It's influenced by Windows, because that's my primary development environment. The Linux interface is the same for consistency.
>>
> 
> But since dll's are usually packaged with the game/app itself, isn't a missing dll something so rare that it is exaggerated to worry about it so much? How can it happen at all, it seems so far-fetched.

I can't think of any games that would ship with OpenGL.  The last time I saw an opengl.dll file in a game, it was an OpenGL wrapper around Glide.

Aah, 3dfx... those were the days :P

> 
>>> Meanwhile, I believe that the disadvantages, such as Derelict being harder/longer to maintain/develop than a regular binding have much more impact. For instance Derelict currently *still* doesn't support MacOS. (Not that I use (or even like) Macs, but it's quite an important platform that should be supported)
>>>
>>> This seems a shame to me, since Derelict is clearly the binding with the most commitment, exposure, and work done on it.
>>
>>
>> Derelict really isn't difficult or time consuming to maintain. I just
> 
> It's more work than a pure static-link binding, no?

Having worked on dynamic-loading cairo bindings, I can say: yes but
kinda not really (see below).

> 
>> don't spend much time on it because I have a lot going on. From the beginning it's been a hobby. Derelict doesn't support Mac because I don't own a Mac. I would dearly love to get Mac support into the trunk. The only reason Linux is supported is because two of the maintainers
> 
> Wouldn't a pure header conversion work in all platforms that the
> original header works in?...
> It doesn't work on Mac, and it didn't work before in Linux because you
> need to add the code to the dynamic binding, right?

As I understand it, those .LIB files you get are basically (guess what) dynamically loading the DLL.  It's just that they're generated by the compiler or somesuch.  So what derelict is doing is exactly what those static .LIB files are doing... except that it throws exceptions instead of hurling a

	"Gaargh!  You system is b0rked!  Run aways!"

dialog at the user's face.  I'd personally much rather be able to show them a

        "You don't appear to have OpenGL 2.0 installed.  Please check
         to make sure you are using the latest drivers for your graphics
         card.  If problems persist, feel free to drop us a line."

message.

In the end, I think it's six of one, a half-dozen of the other.  In the end, the program runs exactly the same; the only differences are very minor, and only happen on program start up.

As for the maintenance, I can't talk about how it's done in Derelict, but with my binding I just have a small script that spits out all the dynamic loading code for me.  All I have to do is take the C header file, and rename a few types to the D equivalent.  What's more, that's basically a one-off.  Once it's done, it's done.

Regarding supported platforms: the main stumbling block, at this point, seems to be std.loader.  It's got some funny license on it that no one seems comfortable with, but as yet it hasn't been replaced (correct me if I'm wrong).  Mac OSX is most certainly capable of dynamically loading libraries, so it's simply a matter of writing that code for Mac OSX.

At the end of the day, I'll continue writing my bindings using dynamic loading, since I feel that it's the most flexible approach, and gives programmers the most control over the process.

As a quick example, the upcoming Cairo 1.2 will add several new
features.  I plan on having a simple mechanism to allow programmers to
say "only load the features from Cairo 1.0", so that they don't get
errors if they try to use a 1.0 DLL with the latest version of the binding.

And you can't do that with a static .LIB :)

	-- Daniel Keep

P.S.  Sorry if this is a bit rambly.  Have MMX assembler on the brain at the moment...

> 
> (Note: I'm not demanding or asking that you should work on static-link binding, as I'm fully aware this is your hobby project and you do whatever you want with it. I'm (mostly) just giving my take on the static vs. dynamic binding issue.)
> 
>>
>> For what it's worth, one of the future additions to Derelict is going to be the ability to link statically with an import library and bypass the runtime loading mechanism.
> 
> That would be great!
> 

-- 

v1sw5+8Yhw5ln4+5pr6OFma8u6+7Lw4Tm6+7l6+7D a2Xs3MSr2e4/6+7t4TNSMb6HTOp5en5g6RAHCP    http://hackerkey.com/
May 21, 2006
Bruno Medeiros wrote:

> 
> But since dll's are usually packaged with the game/app itself, isn't a missing dll something so rare that it is exaggerated to worry about it so much? How can it happen at all, it seems so far-fetched.

That's true for things like SDL and such, but not OpenGL. Sometimes downloads become corrupted, installations get borked, people delete files by accident... there are a lot of variables.

When Herb Marselas was still with Ensemble Studios he wrote an article for Game Programming Gems 2 called "Protect Yourself From DLL Hell and Missing OS Functions" in which he advocated even loading Win32 API DLLs manually. I'm not sure I'd go that far, as there are other applications that would likely barf before a game would. But his reasoning in the article was sound.

In my own experience, I have gotten the missing DLL message more than once, from games and other applications, over the years. As absurd as it sounds, it happens. When your target market is not as computer literate as the hardcore types, it's best to make it as easy on the user as possible.


>>
>> Derelict really isn't difficult or time consuming to maintain. I just 
> 
> It's more work than a pure static-link binding, no?

Not much. The only extra work is declaring the function pointers and implementing the load functions. That's just a matter of copy and paste.  Or for the more industrious, implementing a script to handle it for you.


> 
> Wouldn't a pure header conversion work in all platforms that the original header works in?...
> It doesn't work on Mac, and it didn't work before in Linux because you need to add the code to the dynamic binding, right?

Right. But then that would defeat the purpose of Derelict. Again, the other bindings already link to the import library statically. Derelict is meant to provide an alternative.

> 
>>
>> For what it's worth, one of the future additions to Derelict is going to be the ability to link statically with an import library and bypass the runtime loading mechanism. 
> 
> That would be great!

I'm glad you think so, but I still don't understand why. I flip-flopped on this for a long time, because it means double the effort and I just don't see the benefit of it versus the current mechanism. Manual loading is much more flexible, which to me equates to "better". I can't see any benefit at all to linking statically to the import libraries, other than the fact that you don't have to call a load method as the OS will handle that for you. Still, I have gotten messages from a few people who really see it differently. If adding the feature makes it more convenient for people, then I don't mind, even if I don't understand.
« First   ‹ Prev
1 2 3