Steven Schveighoffer wrote:
On Tue, 22 Sep 2009 22:02:59 -0400, Jeremie Pelletier
<jerem...@gmail.com> wrote:
Yeah most of my display interfaces would make use of covariant
arguments, I use main abstract factory for the entire package, and the
objects it creates contain factory methods themselves. I plan to have
implementations for all of win32, gdk, x11, quartz, cairo, pango, d2d,
dwrite, gl, gl3 and finally d3d7 up to d3d11. Most of the client code
will therefore see only the interfaces in order to maintain
portability, and to allow different implementations to live in the
same executable (for example win32/gl/cairo/pango for up to vista or
win32/d3d/d2d/dwrite if on win7 and up).
Here is a watered down version of a few interfaces I use, which are
used by client code:
interface IDrawable {}
interface IWindow : IDrawable {} // onscreen drawable
interface ISurface : IDrawable {} // offscreen drawable
interface IDisplayContext {} // base of 2d-3d contextes
interface IRenderContext {} // 3d context
interface IWindowRenderContext {} // specialized onscreen 3d context
interface IRenderer {
IWindowRenderContext CreateRenderContext(IWindow);
ISurfaceRenderContext CreateRenderContext(ISurface);
}
And some of their current implementation, which are all used within
the package:
abstract class Win32Drawable : IDrawable {}
final class Win32Window : Win32Drawable, IWindow {}
final class Win32Surface : Win32Drawable, IWindow {}
final class GLRenderer : IRenderer {
GLWindowRenderContext CreateRenderContext(IWindow window) {
if(auto win32Window = cast(Win32Window)window)
return new GLWindowRenderContext(win32Window);
else throw new Error();
}
GLSurfaceRenderContext CreateRenderContext(ISurface surface) {
if(auto win32Surface = cast(Win32Surface)surface)
return new GLSurfaceRenderContext(win32Surface);
else throw new Error();
}
}
abstract class GLRenderContext : IRenderContext {}
final class GLWindowRenderContext : GLRenderContext,
IWindowRenderContext {
this(Win32Window) {}
}
final class GLSurfaceRenderContext : GLRenderContext,
ISurfaceRenderContext {
this(Win32Surface) {}
}
I have over a hundred of such methods doing dynamic casts across all
the different implementations like these twos in this package alone, a
display interface is quite a large beast.
Of course if you can suggest a better way of doing methods expecting a
specific implementation of an object, while still allowing client code
to call them with the interface pointer, I'd be glad to implement it :)
Jeremie
There are some possible solutions. First, it looks like you are using
interfaces to abstract the platform, which seems more appropriate for
version statements. I once wrote an OS abstraction library in C++, and
in my fanatic attempt to avoid using the preprocessor for anything, I
made everything interfaces (pure abstract classes). I think with D, the
version statements are a much better solution, and will reduce overhead
quite a bit.
Second, Your IRenderer is the one responsible for creating a render
context, but it depends on being "hooked up" with the appropriate
IWindow or ISurface object. However, the IRenderer implementation's
methods are pretty much static (granted they might be trimmed down).
Why not move them into the IWindow and ISurface interfaces?
interface IWindow : IDrawable {
IWindowRenderContext CreateRenderContext();
}
interface ISurface : IDrawable {
ISurfaceRenderContext CreateRenderContext();
}
If you need an instance of IRenderer for some other reason not shown,
then consider using a hidden singleton of the correct implementation.
-Steve
Because there will be multiple implementations living in the same
executable. We all know how microsoft likes to force new technology to
new windows versions. I want support for Direct2D, DirectWrite, and all
versions of Direct3D. Which requires different implementations for 7,
vista, and xp. Then some people might have better performance with GL,
for graphic adapter vendor and driver issues, so I'm throwing such an
implementation in the mix. On unix you have a choice of different
windowing toolkits, be it Gnome, Qt, Xfce or directly using X11 but
losing specific features offered by the toolkits.
As for merging IRenderer with the drawables, this wouldn't fit my
design. I use what I call render contexts and paint contexts for 3d and
2d drawing respectively, which are both built upon a common set of
display interfaces to get most of their shared concepts unified and
compatible with one another. I also have font layering and rendering
interfaces usable by the two. And given that many different render and
paint implementations can be used from the same drawable targets, it
wouldn't make sense.
My first design was using version statements but it was far from
flexible enough for my needs. The overhead is mostly needed to allocate
the interfaces, not to use them so the speed isn't affected.
Then you get my I/O interface which roots at simple input/output
streams, then seekable streams, binary streams, file streams, async
streams, pipes, etc. And get different implementations for local
filesystem I/O, sockets, specialized file format abstractions, and
whatnot. So for example a method expecting an IInputStream does not care
what is implementing it, so long as it has a read method implemented, be
it reading data from a file, from a network connection, from a packed
file within an archive. These implementations still need covariant
parameters within themselves for a few things.
Jeremie