Interface system refactoring
* lumiera_interface are now static structures, never wrritten * introduced a lumiera_interfacenode which manages the dynamic data of interfaces. The interfaceregistry now holds this nodes.
This commit is contained in:
parent
405a578c42
commit
f05f6772f1
4 changed files with 177 additions and 123 deletions
|
|
@ -36,18 +36,22 @@
|
|||
*/
|
||||
|
||||
|
||||
extern LumieraInterface lumiera_interface_stack;
|
||||
static LumieraInterfacenode
|
||||
lumiera_interface_open_interfacenode (LumieraInterfacenode self);
|
||||
|
||||
static void
|
||||
lumiera_interfacenode_close (LumieraInterfacenode self);
|
||||
|
||||
|
||||
LumieraInterface
|
||||
lumiera_interface_open (const char* interface, unsigned version, size_t minminorversion, const char* name)
|
||||
{
|
||||
LumieraInterface self = NULL;
|
||||
LumieraInterfacenode self = NULL;
|
||||
TRACE (interface, "%s", name);
|
||||
|
||||
LUMIERA_RECMUTEX_SECTION (interfaceregistry, &lumiera_interface_mutex)
|
||||
{
|
||||
self = lumiera_interfaceregistry_interface_find (interface, version, name);
|
||||
self = lumiera_interfaceregistry_interfacenode_find (interface, version, name);
|
||||
|
||||
if (!self)
|
||||
{
|
||||
|
|
@ -56,33 +60,33 @@ lumiera_interface_open (const char* interface, unsigned version, size_t minminor
|
|||
|
||||
if (self)
|
||||
{
|
||||
if (minminorversion > self->size)
|
||||
if (minminorversion > self->interface->size)
|
||||
{
|
||||
UNIMPLEMENTED ("set error");
|
||||
self = NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
self = lumiera_interface_open_interface (self);
|
||||
self = lumiera_interface_open_interfacenode (self);
|
||||
}
|
||||
}
|
||||
}
|
||||
return self;
|
||||
return self->interface;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
push_dependency (LumieraInterface parent, LumieraInterface child)
|
||||
push_dependency (LumieraInterfacenode parent, LumieraInterfacenode child)
|
||||
{
|
||||
/* push a dependency on the dependency array, allcoate or resize it on demand */
|
||||
TRACE (interface, "%s %s", parent->name, child->name);
|
||||
TRACE (interface, "%s %s", parent->interface->name, child->interface->name);
|
||||
|
||||
/* no dependencies recorded yet, alloc a first block for 4 pointers */
|
||||
if (!parent->ndeps)
|
||||
parent->deps = lumiera_calloc (parent->ndeps = 4, sizeof (LumieraInterface));
|
||||
if (!parent->deps_size)
|
||||
parent->deps = lumiera_calloc (parent->deps_size = 4, sizeof (LumieraInterfacenode));
|
||||
|
||||
size_t sz = parent->ndeps;
|
||||
LumieraInterface* itr = parent->deps;
|
||||
size_t sz = parent->deps_size;
|
||||
LumieraInterfacenode* itr = parent->deps;
|
||||
|
||||
while (*itr)
|
||||
{
|
||||
|
|
@ -91,33 +95,31 @@ push_dependency (LumieraInterface parent, LumieraInterface child)
|
|||
if (sz == 1)
|
||||
{
|
||||
/* block to small, realloc it with twice its size, we keep the block NULL terminated */
|
||||
sz = parent->ndeps + 1;
|
||||
parent->ndeps *= 2;
|
||||
parent->deps = lumiera_realloc (parent->deps, parent->ndeps * sizeof (LumieraInterface));
|
||||
sz = parent->deps_size + 1;
|
||||
parent->deps_size *= 2;
|
||||
parent->deps = lumiera_realloc (parent->deps, parent->deps_size * sizeof (LumieraInterface));
|
||||
itr = parent->deps + sz - 2;
|
||||
memset (itr, 0, sz * sizeof (LumieraInterface));
|
||||
}
|
||||
}
|
||||
|
||||
TODO ("free the deps when unregistering the interface");
|
||||
|
||||
/* found free element, store self in dependencies */
|
||||
*itr = child;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
depwalk (LumieraInterface self, LumieraInterface* stack)
|
||||
depwalk (LumieraInterfacenode self, LumieraInterfacenode* stack)
|
||||
{
|
||||
/* increment refcount for all non-cyclic dependencies recursively */
|
||||
if (self->deps)
|
||||
{
|
||||
TRACE (interface, "%s %d", self->name, self->refcnt);
|
||||
for (LumieraInterface* dep = self->deps; *dep; ++dep)
|
||||
TRACE (interface, "%s %d", self->interface->name, self->refcnt);
|
||||
for (LumieraInterfacenode* dep = self->deps; *dep; ++dep)
|
||||
{
|
||||
TRACE (interface, "loop %s", (*dep)->name);
|
||||
TRACE (interface, "loop %s", (*dep)->interface->name);
|
||||
int cycle = 0;
|
||||
for (LumieraInterface itr = *stack; itr; itr = itr->lnk)
|
||||
for (LumieraInterfacenode itr = *stack; itr; itr = itr->lnk)
|
||||
{
|
||||
if (itr == *dep)
|
||||
{
|
||||
|
|
@ -144,11 +146,11 @@ depwalk (LumieraInterface self, LumieraInterface* stack)
|
|||
}
|
||||
|
||||
|
||||
LumieraInterface
|
||||
lumiera_interface_open_interface (LumieraInterface self)
|
||||
static LumieraInterfacenode
|
||||
lumiera_interface_open_interfacenode (LumieraInterfacenode self)
|
||||
{
|
||||
static unsigned collect_dependencies = 0;
|
||||
static LumieraInterface stack = NULL;
|
||||
static LumieraInterfacenode stack = NULL;
|
||||
|
||||
/*
|
||||
Ok, this got little more complicated than it should be,
|
||||
|
|
@ -157,14 +159,14 @@ lumiera_interface_open_interface (LumieraInterface self)
|
|||
|
||||
if (self)
|
||||
{
|
||||
TRACE (interface, "%s %d (%s)", self->name, self->refcnt, stack?stack->name:"");
|
||||
TRACE (interface, "%s %d (%s)", self->interface->name, self->refcnt, stack?stack->interface->name:"");
|
||||
|
||||
LUMIERA_RECMUTEX_SECTION (interfaceregistry, &lumiera_interface_mutex)
|
||||
{
|
||||
/* discover cycles, cycles don't refcount! */
|
||||
int cycle = 0;
|
||||
|
||||
for (LumieraInterface itr = stack; itr; itr = itr->lnk)
|
||||
for (LumieraInterfacenode itr = stack; itr; itr = itr->lnk)
|
||||
{
|
||||
if (itr == self)
|
||||
{
|
||||
|
|
@ -188,11 +190,11 @@ lumiera_interface_open_interface (LumieraInterface self)
|
|||
if (self->refcnt == 1)
|
||||
{
|
||||
/* first opening, run acquire, recursive opening shall record its dependencies here */
|
||||
if (self->acquire)
|
||||
if (self->interface->acquire)
|
||||
{
|
||||
TRACE (interface, "Acquire %s", self->name);
|
||||
TRACE (interface, "Acquire %s", self->interface->name);
|
||||
collect_dependencies = self->deps?0:1;
|
||||
self = self->acquire (self);
|
||||
self->interface = self->interface->acquire (self->interface);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
@ -216,60 +218,69 @@ lumiera_interface_open_interface (LumieraInterface self)
|
|||
void
|
||||
lumiera_interface_close (LumieraInterface self)
|
||||
{
|
||||
static LumieraInterface stack = NULL;
|
||||
TRACE (interface);
|
||||
|
||||
LUMIERA_RECMUTEX_SECTION (interfaceregistry, &lumiera_interface_mutex)
|
||||
{
|
||||
lumiera_interfacenode_close ((LumieraInterfacenode)psplay_find (lumiera_interfaceregistry, self, 100));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* internal function, does no locking! */
|
||||
static void
|
||||
lumiera_interfacenode_close (LumieraInterfacenode self)
|
||||
{
|
||||
static LumieraInterfacenode stack = NULL;
|
||||
|
||||
if (!self)
|
||||
return;
|
||||
|
||||
TRACE (interface, "%s %d (%s)", self->name, self->refcnt, stack?stack->name:"");
|
||||
TRACE (interface, "%s %d (%s)", self->interface->name, self->refcnt, stack?stack->interface->name:"");
|
||||
|
||||
LUMIERA_RECMUTEX_SECTION (interfaceregistry, &lumiera_interface_mutex)
|
||||
REQUIRE (self->refcnt);
|
||||
|
||||
int cycle = 0;
|
||||
|
||||
for (LumieraInterfacenode itr = stack; itr; itr = itr->lnk)
|
||||
{
|
||||
REQUIRE (self->refcnt);
|
||||
|
||||
int cycle = 0;
|
||||
|
||||
for (LumieraInterface itr = stack; itr; itr = itr->lnk)
|
||||
if (itr == self)
|
||||
{
|
||||
if (itr == self)
|
||||
{
|
||||
TRACE (interface, "CYCLE");
|
||||
cycle = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!cycle)
|
||||
{
|
||||
self->lnk = stack;
|
||||
stack = self;
|
||||
|
||||
if (self->refcnt == 1)
|
||||
{
|
||||
if (self->release)
|
||||
{
|
||||
TRACE (interface, "Release %s", self->name);
|
||||
self->release (self);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (self->deps)
|
||||
{
|
||||
TRACE (interface, "Recurse %s %d", self->name, self->refcnt);
|
||||
|
||||
for (LumieraInterface* dep = self->deps; *dep; ++dep)
|
||||
lumiera_interface_close (*dep);
|
||||
}
|
||||
}
|
||||
|
||||
stack = self->lnk;
|
||||
self->lnk = NULL;
|
||||
--self->refcnt;
|
||||
TRACE (interface, "CYCLE");
|
||||
cycle = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!cycle)
|
||||
{
|
||||
self->lnk = stack;
|
||||
stack = self;
|
||||
|
||||
if (self->refcnt == 1)
|
||||
{
|
||||
if (self->interface->release)
|
||||
{
|
||||
TRACE (interface, "Release %s", self->interface->name);
|
||||
self->interface->release (self->interface);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (self->deps)
|
||||
{
|
||||
TRACE (interface, "Recurse %s %d", self->interface->name, self->refcnt);
|
||||
|
||||
for (LumieraInterfacenode* dep = self->deps; *dep; ++dep)
|
||||
lumiera_interfacenode_close (*dep);
|
||||
}
|
||||
}
|
||||
|
||||
stack = self->lnk;
|
||||
self->lnk = NULL;
|
||||
--self->refcnt;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
#include "lib/mutex.h"
|
||||
#include "lib/error.h"
|
||||
#include "lib/psplay.h"
|
||||
#include "lib/safeclib.h"
|
||||
|
||||
|
||||
#include <nobug.h>
|
||||
|
|
@ -41,10 +42,9 @@ NOBUG_DEFINE_FLAG_PARENT (interface_all, backend);
|
|||
NOBUG_DEFINE_FLAG_PARENT (interfaceregistry, interface_all);
|
||||
NOBUG_DEFINE_FLAG_PARENT (interface, interface_all);
|
||||
|
||||
static PSplay interfaceregistry;
|
||||
PSplay lumiera_interfaceregistry;
|
||||
lumiera_mutex lumiera_interface_mutex;
|
||||
|
||||
|
||||
static int
|
||||
cmp_fn (const void* keya, const void* keyb);
|
||||
|
||||
|
|
@ -52,6 +52,34 @@ static const void*
|
|||
key_fn (const PSplaynode node);
|
||||
|
||||
|
||||
static LumieraInterfacenode
|
||||
lumiera_interfacenode_new (LumieraInterface iface)
|
||||
{
|
||||
LumieraInterfacenode self = lumiera_malloc (sizeof (*self));
|
||||
|
||||
psplaynode_init (&self->node);
|
||||
self->interface = iface;
|
||||
self->refcnt = 0;
|
||||
self->lnk = NULL;
|
||||
self->deps_size = 0;
|
||||
self->deps = NULL;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
lumiera_interfacenode_delete (LumieraInterfacenode self)
|
||||
{
|
||||
if (self)
|
||||
{
|
||||
REQUIRE (self->refcnt == 0);
|
||||
lumiera_free (self->deps);
|
||||
lumiera_free (self);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Initialize the interface registry
|
||||
|
|
@ -63,28 +91,27 @@ lumiera_interfaceregistry_init (void)
|
|||
NOBUG_INIT_FLAG (interfaceregistry);
|
||||
NOBUG_INIT_FLAG (interface);
|
||||
TRACE (interfaceregistry);
|
||||
REQUIRE (!interfaceregistry);
|
||||
REQUIRE (!lumiera_interfaceregistry);
|
||||
|
||||
TODO ("introduce a registrynode structure, place all dynamic interface stuff there, make interface_struct const");
|
||||
|
||||
interfaceregistry = psplay_new (cmp_fn, key_fn, NULL);
|
||||
if (!interfaceregistry)
|
||||
lumiera_interfaceregistry = psplay_new (cmp_fn, key_fn, NULL);
|
||||
if (!lumiera_interfaceregistry)
|
||||
LUMIERA_DIE (ERRNO);
|
||||
|
||||
lumiera_recmutex_init (&lumiera_interface_mutex, "interfaceregistry", &NOBUG_FLAG(interfaceregistry));
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
lumiera_interfaceregistry_destroy (void)
|
||||
{
|
||||
TRACE (interfaceregistry);
|
||||
REQUIRE (!psplay_nelements (interfaceregistry));
|
||||
REQUIRE (!psplay_nelements (lumiera_interfaceregistry));
|
||||
|
||||
lumiera_mutex_destroy (&lumiera_interface_mutex, &NOBUG_FLAG(interfaceregistry));
|
||||
|
||||
if (interfaceregistry)
|
||||
psplay_destroy (interfaceregistry);
|
||||
interfaceregistry = NULL;
|
||||
if (lumiera_interfaceregistry)
|
||||
psplay_destroy (lumiera_interfaceregistry);
|
||||
lumiera_interfaceregistry = NULL;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -97,7 +124,7 @@ lumiera_interfaceregistry_register_interface (LumieraInterface self)
|
|||
LUMIERA_RECMUTEX_SECTION (interfaceregistry, &lumiera_interface_mutex)
|
||||
{
|
||||
TRACE (interfaceregistry, "interface %s, version %d, instance %s", self->interface, self->version, self->name);
|
||||
psplay_insert (interfaceregistry, &self->node, 100);
|
||||
psplay_insert (lumiera_interfaceregistry, &lumiera_interfacenode_new (self)->node, 100);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -113,7 +140,7 @@ lumiera_interfaceregistry_bulkregister_interfaces (LumieraInterface* self)
|
|||
while (*self)
|
||||
{
|
||||
TRACE (interfaceregistry, "interface %s, version %d, instance %s", (*self)->interface, (*self)->version, (*self)->name);
|
||||
psplay_insert (interfaceregistry, &(*self)->node, 100);
|
||||
psplay_insert (lumiera_interfaceregistry, &lumiera_interfacenode_new (*self)->node, 100);
|
||||
++self;
|
||||
}
|
||||
}
|
||||
|
|
@ -128,10 +155,10 @@ lumiera_interfaceregistry_remove_interface (LumieraInterface self)
|
|||
|
||||
LUMIERA_RECMUTEX_SECTION (interfaceregistry, &lumiera_interface_mutex)
|
||||
{
|
||||
REQUIRE (self->refcnt == 0);
|
||||
psplay_remove (interfaceregistry, &self->node);
|
||||
LumieraInterfacenode node = (LumieraInterfacenode) psplay_find (lumiera_interfaceregistry, self, 0);
|
||||
REQUIRE (node->refcnt == 0, "but is %d", node->refcnt);
|
||||
|
||||
FIXME ("free deps");
|
||||
lumiera_interfacenode_delete ((LumieraInterfacenode)psplay_remove (lumiera_interfaceregistry, &node->node));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -147,18 +174,20 @@ lumiera_interfaceregistry_bulkremove_interfaces (LumieraInterface* self)
|
|||
while (*self)
|
||||
{
|
||||
TRACE (interfaceregistry, "interface %s, version %d, instance %s", (*self)->interface, (*self)->version, (*self)->name);
|
||||
REQUIRE ((*self)->refcnt == 0, "but is %d", (*self)->refcnt);
|
||||
|
||||
psplay_remove (interfaceregistry, &(*self)->node);
|
||||
LumieraInterfacenode node = (LumieraInterfacenode) psplay_find (lumiera_interfaceregistry, *self, 0);
|
||||
REQUIRE (node->refcnt == 0, "but is %d", node->refcnt);
|
||||
|
||||
lumiera_interfacenode_delete ((LumieraInterfacenode) psplay_remove (lumiera_interfaceregistry, &node->node));
|
||||
|
||||
++self;
|
||||
FIXME ("free deps");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
LumieraInterface
|
||||
lumiera_interfaceregistry_interface_find (const char* interface, unsigned version, const char* name)
|
||||
LumieraInterfacenode
|
||||
lumiera_interfaceregistry_interfacenode_find (const char* interface, unsigned version, const char* name)
|
||||
{
|
||||
TRACE (interfaceregistry);
|
||||
struct lumiera_interface_struct cmp;
|
||||
|
|
@ -166,17 +195,24 @@ lumiera_interfaceregistry_interface_find (const char* interface, unsigned versio
|
|||
cmp.version = version;
|
||||
cmp.name = name;
|
||||
|
||||
LumieraInterface ret = NULL;
|
||||
LumieraInterfacenode ret = NULL;
|
||||
|
||||
LUMIERA_RECMUTEX_SECTION (interfaceregistry, &lumiera_interface_mutex)
|
||||
{
|
||||
ret = (LumieraInterface)psplay_find (interfaceregistry, &cmp, 100);
|
||||
ret = (LumieraInterfacenode)psplay_find (lumiera_interfaceregistry, &cmp, 100);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
LumieraInterface
|
||||
lumiera_interfaceregistry_interface_find (const char* interface, unsigned version, const char* name)
|
||||
{
|
||||
return lumiera_interfaceregistry_interfacenode_find (interface, version, name)->interface;
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
cmp_fn (const void* keya, const void* keyb)
|
||||
{
|
||||
|
|
@ -207,7 +243,7 @@ cmp_fn (const void* keya, const void* keyb)
|
|||
static const void*
|
||||
key_fn (const PSplaynode node)
|
||||
{
|
||||
return node;
|
||||
return ((LumieraInterfacenode)node)->interface;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
#define LUMIERA_INTERFACEREGISTRY_H
|
||||
|
||||
#include "lib/mutex.h"
|
||||
#include "lib/psplay.h"
|
||||
#include "lib/interface.h"
|
||||
|
||||
#include <nobug.h>
|
||||
|
|
@ -38,9 +39,37 @@ NOBUG_DECLARE_FLAG (interface_all);
|
|||
NOBUG_DECLARE_FLAG (interfaceregistry);
|
||||
NOBUG_DECLARE_FLAG (interface);
|
||||
|
||||
extern PSplay lumiera_interfaceregistry;
|
||||
extern lumiera_mutex lumiera_interface_mutex;
|
||||
|
||||
|
||||
/**
|
||||
* Interface management node.
|
||||
* All active interfaces managed through this node which contains the dynamic data for
|
||||
* dependency tracking and reference counting.
|
||||
*/
|
||||
typedef struct lumiera_interfacenode_struct lumiera_interfacenode;
|
||||
typedef lumiera_interfacenode* LumieraInterfacenode;
|
||||
|
||||
struct lumiera_interfacenode_struct
|
||||
{
|
||||
/** all known interfaces are registered in a tree */
|
||||
psplaynode node;
|
||||
|
||||
/** the interface itself */
|
||||
LumieraInterface interface;
|
||||
|
||||
/** reference counters and link used for internal reference management */
|
||||
unsigned refcnt;
|
||||
/** temporary used to stack interfaces when recursively opening/closing them */
|
||||
LumieraInterfacenode lnk;
|
||||
/** allocated size of the following deps table */
|
||||
size_t deps_size;
|
||||
/** NULL terminated table of all dependenncies (interfaces opened on initialization) */
|
||||
LumieraInterfacenode* deps;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Initialize the interface registry
|
||||
*/
|
||||
|
|
@ -63,6 +92,9 @@ lumiera_interfaceregistry_remove_interface (LumieraInterface self);
|
|||
void
|
||||
lumiera_interfaceregistry_bulkremove_interfaces (LumieraInterface* self);
|
||||
|
||||
LumieraInterfacenode
|
||||
lumiera_interfaceregistry_interfacenode_find (const char* interface, unsigned version, const char* name);
|
||||
|
||||
LumieraInterface
|
||||
lumiera_interfaceregistry_interface_find (const char* interface, unsigned version, const char* name);
|
||||
|
||||
|
|
|
|||
|
|
@ -182,15 +182,10 @@ PPMPL_FOREACH(_P1_, __VA_ARGS__)
|
|||
LUMIERA_INTERFACE_TYPE(iname, version) LUMIERA_INTERFACE_DNAME(iname, version, name) = \
|
||||
{ \
|
||||
{ \
|
||||
PSPLAYNODE_INITIALIZER, \
|
||||
#iname, \
|
||||
version, \
|
||||
#name, \
|
||||
sizeof (LUMIERA_INTERFACE_TYPE(iname, version)), \
|
||||
0, \
|
||||
NULL, \
|
||||
0, \
|
||||
NULL, \
|
||||
descriptor, \
|
||||
acquire, \
|
||||
release \
|
||||
|
|
@ -342,9 +337,6 @@ struct lumiera_interfaceslot_struct
|
|||
*/
|
||||
struct lumiera_interface_struct
|
||||
{
|
||||
/** all known interfaces are registered in a tree */
|
||||
psplaynode node;
|
||||
|
||||
/** name of the interface (type) */
|
||||
const char* interface;
|
||||
|
||||
|
|
@ -357,12 +349,6 @@ struct lumiera_interface_struct
|
|||
/** size of the whole interface structure (minor version) */
|
||||
size_t size;
|
||||
|
||||
/** reference counters and link used for internal reference management */
|
||||
unsigned refcnt;
|
||||
LumieraInterface lnk;
|
||||
size_t ndeps;
|
||||
LumieraInterface* deps;
|
||||
|
||||
/** metadata descriptor, itself a interface (or NULL) */
|
||||
LumieraInterface descriptor;
|
||||
|
||||
|
|
@ -391,17 +377,6 @@ struct lumiera_interface_struct
|
|||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Open an interface by handle.
|
||||
* This is faster because it needs no lookup, one must be sure to have a valid handle
|
||||
* which mets the requirements (version)
|
||||
* @param self pointer to the interface to be opened
|
||||
* @return self on success or NULL on error
|
||||
* @internal This function is mostly for internal purposes
|
||||
*/
|
||||
LumieraInterface
|
||||
lumiera_interface_open_interface (LumieraInterface self);
|
||||
|
||||
/**
|
||||
* Open an interface by version and name.
|
||||
* Looks up the requested interface, possibly loading it from a plugin.
|
||||
|
|
|
|||
Loading…
Reference in a new issue