Jump to page: 1 2
Thread overview
Static constructor
Jan 06
ludo
Jan 12
ludo
Jan 15
ludo
Jan 17
ludo
Jan 18
ludo
January 06
I read in the documentation
"Static constructors are used to initialize static class members with values that cannot be computed at compile time"

I try to understand the design of the following code:

---
class OpenAL
{
	static string[int] ALErrorLookup;
	static Object mutex;

	// Initialize static variables
	static this ()
	{	ALErrorLookup = [
			0xA001: "AL_INVALID_NAME"[],
			0xA002: "AL_ILLEGAL_ENUM",
			0xA002: "AL_INVALID_ENUM",
			0xA003: "AL_INVALID_VALUE",
			0xA004: "AL_ILLEGAL_COMMAND",
			0xA004: "AL_INVALID_OPERATION",
			0xA005: "AL_OUT_OF_MEMORY"
		];
		mutex = new Object();
	}

       static anotherfunc()
       {}

	static Object getMutex()
	{	return mutex;
        }
}
---

At this point, I have not looked up Object, guess must be a class. It seems to me that ALErrorLookup can be computed at compile time... So the constructor is static because mutex "can not be computed at compiled time"?

The idea of the dev (from context) is to that this class will just be a wrapper, no instance necessary. So anotherfunc(), which does the main work, is static and everything goes this way.

Then getMutex returns the static mutex when necessary... Have not looked that up yet either.

But, I don't know, i have a feeling that this is over complicated. For example, can't we have AlErrorlook-up initialized another way in D, a static mutex in the getMutex function directly (with if (mutex == null) {mutex = new Object()} .

I don't know, is it the proper D way? And also, when we have those classes with everything static, does it even make sense to have a class? This module actually contains only this class (https://tinyurl.com/yxt2xw23) Shouldn't we have one module with normal functions?

ANY input is learning material for me. Thanks.




January 06
On Wednesday, 6 January 2021 at 17:05:02 UTC, ludo wrote:
> I read in the documentation
> "Static constructors are used to initialize static class members with values that cannot be computed at compile time"
>
> [...]

Since this is not the complete code it's a bit hard to know, but I'd guess this is for use as a singleton and/or for thread safety.
January 07
On Wednesday, 6 January 2021 at 17:05:02 UTC, ludo wrote:
> ...

Using a static class like this seems to mostly be a design decision.

i.e. instead of using something like

```
openalGetMutex();

// OR

OpenAL openal = ...;
openal.getMutex();

// OR I guess even this

g_openal.getMutex();
```

They specifically want the interface to look like:

```
OpenAL.getMutex();
```

So in otherwords, this class is essentially being used as a unique namespace for all of the other functions. Another way to look at it is just a singleton without a `.instance` getter.

As for some of the other stuff, Associative Array literals in D can't actually be used at compile-time (at least, last time I tried), so they have to be created inside of the static constructor.

Next is the mutex. `Object` is the base class that every class in D will implicitly inherit from, like in C# for example. Object has a `.monitor` field which is basically just a mutex, so is used with multi-threading synchronisation. I find it a bit odd that they're just returning an object instead of a Mutex (module core.sync.mutex), but I'm sure there's a reason why.

(The README of this project states that this is a resurrection of some old project, and even refers to D as "Digital Mars", so this code may or may not have been built on a very old version of D originally.)

A better explanation of the monitor field can be found: https://forum.dlang.org/post/op.vqi9vrqueav7ka@steve-laptop

Hope this answers some questions.
January 07
On Thursday, 7 January 2021 at 01:55:07 UTC, SealabJaster wrote:
> ...

And on a side note, I don't believe this code is working as the author intends.

Someone correct me if I'm wrong, but `static` variables in D arethread-local, so a `static Object mutex` wouldn't actually be visible between threads unless passed via some other means, and would instead act as a mutex-per-thread, sort of defying the point?

However `__gshared Object mutex` would likely be what they want?

I don't do much multithreaded stuff in D, so that information may or may not be correct.
January 12
NOTE : the entire code we are talking about is in the tiny url in my previous post.

On Thursday, 7 January 2021 at 01:55:07 UTC, SealabJaster wrote:
> On Wednesday, 6 January 2021 at 17:05:02 UTC, ludo wrote:
>> ...
>
> Using a static class like this seems to mostly be a design decision.
> So in otherwords, this class is essentially being used as a unique namespace for all of the other functions. Another way to look at it is just a singleton without a `.instance` getter.

Ok, I agree that ends up being a kind of strange singleton. But yes it was D v1 code. Do we agree that the following multi-threaded singleton pattern is the proper way as of today, instead of this static functions in a non static class (in multithreaded environment)?
----
shared(T) singleton()
{
 static shared T instance;

 if(!instance)
 {
   synchronized
   {
     if(!instance)
     instance = new shared(T)();
   }
 }

 return instance;
}
---

>
> As for some of the other stuff, Associative Array literals in D can't actually be used at compile-time (at least, last time I tried), so they have to be created inside of the static constructor.

OK, I found https://dlang.org/spec/hash-map.html#static_initialization
Quote from that doc:
"Static Initialization of AAs
 NOTE: Not yet implemented. "
If I understand, as of today an AA init depends on a runtime function for historical reasons. A bit weird for an array indeed perfectly known at compile time, but someday some  core language contributor will have a look at it, I guess.

>
> Next is the mutex. `Object` is the base class that every class in D will implicitly inherit from, like in C# for example. Object has a `.monitor` field which is basically just a mutex, so is used with multi-threading synchronisation. I find it a bit odd that they're just returning an object instead of a Mutex (module core.sync.mutex), but I'm sure there's a reason why.

If we consider the class from my first post, the comment coming with the getMutex func is following in the original code that I am "cleaning-up":
"Get an OpenAL mutex to ensure that no two threads ever execute OpenAL functionality simultaneously."

But the OpenAL class only has one function (anotherFunc), and as you say SealabJaster, it looks odd to use this Object mutex. I see two options to replace this mutex thing:
* make the entire class synchronized (writing "synchronized class OpenAL")
* make the anotherFunc function synchronized. So just one keyword to add!
If I understand well, this will accomplish the goal quoted with no further comestics needed! Only one keyword over explicitely using object mutex. Am I right?

Thanks

January 14
On Tuesday, 12 January 2021 at 11:28:12 UTC, ludo wrote:
> Ok, I agree that ends up being a kind of strange singleton. But yes it was D v1 code. Do we agree that the following multi-threaded singleton pattern is the proper way as of today.

It looks fine to me. The D wiki has the following example which prevents the need for entering the synchronized block more than once per thread: https://wiki.dlang.org/Low-Lock_Singleton_Pattern

I should note that the example doesn't specify the value as shared, which technically isn't the correct thing to do, but `shared` itself can be a bit of an annoyance. However, you do need to ensure that your static variable is either shared or __gshared, otherwise it becomes thread-local.

> If I understand, as of today an AA init depends on a runtime function for historical reasons. A bit weird for an array indeed perfectly known at compile time, but someday some  core language contributor will have a look at it, I guess.

Maybe in 5 years' time we'll get an uneventful discussion on it where it'd take 2 years to come to a conclusion before another year for it to actually be implemented.

> If I understand well, this will accomplish the goal quoted with no further comestics needed! Only one keyword over explicitely using object mutex. Am I right?

I believe so. I've never used OpenAL so it may have additional restrictions with multithreading, but from a simple "This function is only ever executed on one thread at a time", your above suggestions should work.

Apologies for the late reply.

January 15
> I believe so. I've never used OpenAL so it may have additional restrictions with multithreading, but from a simple "This function is only ever executed on one thread at a time", your above suggestions should work.
>
> Apologies for the late reply.

No worry and thank you. I found the low-lock pattern on my own, digging more info. Smart pattern!
I put the synchronized function in front of the function. The problem with multithreading, is the difficulty to verify that it works fine :) But now the topic streams away from static constructor :) Cheers
January 15
On Friday, 15 January 2021 at 16:04:02 UTC, ludo wrote:
>> I believe so. I've never used OpenAL so it may have additional restrictions with multithreading, but from a simple "This function is only ever executed on one thread at a time", your above suggestions should work.
>>
>> Apologies for the late reply.
>
> No worry and thank you. I found the low-lock pattern on my own, digging more info. Smart pattern!
> I put the synchronized function in front of the function. The problem with multithreading, is the difficulty to verify that it works fine :) But now the topic streams away from static constructor :) Cheers

Isn't that kind of the de facto standard? Well, saw this was 7 years ago now ūüėĀ
January 15
On 1/6/21 12:05 PM, ludo wrote:
> I read in the documentation
> "Static constructors are used to initialize static class members with values that cannot be computed at compile time"
> 
> I try to understand the design of the following code:
> 
> ---
> class OpenAL
> {
>      static string[int] ALErrorLookup;
>      static Object mutex;
> 
>      // Initialize static variables
>      static this ()
>      {    ALErrorLookup = [
>              0xA001: "AL_INVALID_NAME"[],
>              0xA002: "AL_ILLEGAL_ENUM",
>              0xA002: "AL_INVALID_ENUM",
>              0xA003: "AL_INVALID_VALUE",
>              0xA004: "AL_ILLEGAL_COMMAND",
>              0xA004: "AL_INVALID_OPERATION",
>              0xA005: "AL_OUT_OF_MEMORY"
>          ];
>          mutex = new Object();
>      }
> 
>         static anotherfunc()
>         {}
> 
>      static Object getMutex()
>      {    return mutex;
>          }
> }
> ---
> 
> At this point, I have not looked up Object, guess must be a class. It seems to me that ALErrorLookup can be computed at compile time... So the constructor is static because mutex "can not be computed at compiled time"?

Associative arrays can be computed at compile time, and used at compile time. BUT they cannot be transferred to runtime AAs (yet).

So likely that is the reason the AA initialization is in there.

You can (now) create classes at compile-time, and then assign them to variables as static initializers. I don't know if that means you can create the mutex at compile-time. I know OS mutex primitives can be initialized at compile time. I don't know if that works properly here.

Also note that what is happening here is not correct in D2 (I see that it's D1 you noted in a subsequent message), as `static this()` is run once per thread (and mutex is going to get one instance per thread). So most likely you need to change both of these to shared.

> 
> The idea of the dev (from context) is to that this class will just be a wrapper, no instance necessary. So anotherfunc(), which does the main work, is static and everything goes this way.
> 
> Then getMutex returns the static mutex when necessary... Have not looked that up yet either.

It doesn't make a whole lot of sense, since mutex is publicly accessible.

> But, I don't know, i have a feeling that this is over complicated. For example, can't we have AlErrorlook-up initialized another way in D, a static mutex in the getMutex function directly (with if (mutex == null) {mutex = new Object()} .

No on the AA (as noted above). The mutex *is* created on demand. Every Object can have a mutex, and it's only created when you synchronize it for the first time.

> I don't know, is it the proper D way? And also, when we have those classes with everything static, does it even make sense to have a class? This module actually contains only this class (https://tinyurl.com/yxt2xw23) Shouldn't we have one module with normal functions?
> 
> ANY input is learning material for me. Thanks.

I would say the AA initialization is standard D. Using the class as a namespace isn't standard or necessary. If anything, it should be a struct, or you can use a template. But I can't see why you can't just use a module.

-Steve
January 17
Thanks, as explained I am indeed porting old code.

> No on the AA (as noted above). The mutex *is* created on demand. Every Object can have a mutex, and it's only created when you synchronize it for the first time.

Yes alright. I think the dev made a design mistake because he called synchronized( OpenAL.mutex ) when it should be more of a global, non OpenAL specific, mutex. I mean in the code there were things like  (pseudo-code)
---
System {
  private int[] buffer

  func() {
    synch (openal.mutex)
    {
      openal.dosmthg (buffer)
      buffer.change() // buffer being the member of System
      openal.dosmthg(buffer)
    }
  }
}
---
Basically, it's the entire buffers handling ( the 3 statements) which should be done the "atomic" way, not just the calls to openAL. So the mutex should be a member variable of System at worse. I just replaced
synchronized ( openal.mutex )  {
by
synchronized { // create a global mutex

Of course synchronized (open.mutex) would probably work, but getting the mutex of an abstract class which is used only in part of the calls looks like a design error to me!

>
> I would say the AA initialization is standard D. Using the class as a namespace isn't standard or necessary. If anything, it should be a struct, or you can use a template. But I can't see why you can't just use a module.

I replaced the AA entirely (by a switch in a nested function!). Got rid of the static init() then.

I replaced 'class' by 'struct', which does not oblige me to change the entire code using the syntax "OpenAL.smthg" . But I was tempted to go to the next step:
import OpenAL = ...openal; // should not have to change any code either if it works!

I just have to verify all the imports in the other modules and I can delete the "struct OpenAL" scope. I don't have enough experience in D2 to think about the impact of such a change though, ie from *****
---
// openal.d file
module a.b.OpenAL

struct OpenAL{ void dosmthg()}

// another file
import a.b.OpenAL

OpenAL.dosmthg()
---

******** to *******
---
// openal.d file
module a.b.OpenAL

void dosmthg()

// another file
import OpenAL = a.b.OpenAL

OpenAL.dosmthg()
---

Thanks again for all the info so far. Learning a lot.
« First   ‹ Prev
1 2