added experimental extension interface for loading external extensions from mm:s

hard-bumped the extension api

--HG--
extra : convert_revision : svn%3A39bc706e-5318-0410-9160-8a85361fbb7c/trunk%401654
This commit is contained in:
David Anderson 2007-10-28 08:02:24 +00:00
parent 0cf6a24414
commit f60369a005
3 changed files with 374 additions and 92 deletions

View File

@ -41,14 +41,26 @@
CExtensionManager g_Extensions;
IdentityType_t g_ExtType;
CExtension::CExtension(const char *filename, char *error, size_t maxlength)
void CExtension::Initialize(const char *filename, const char *path)
{
m_File.assign(filename);
m_pAPI = NULL;
m_pIdentToken = NULL;
m_PlId = 0;
unload_code = 0;
m_FullyLoaded = false;
m_bFullyLoaded = false;
m_File.assign(filename);
m_Path.assign(path);
}
CRemoteExtension::CRemoteExtension(IExtensionInterface *pAPI, const char *filename, const char *path)
{
m_pAPI = pAPI;
Initialize(filename, path);
}
CLocalExtension::CLocalExtension(const char *filename)
{
m_PlId = 0;
m_pLib = NULL;
char path[PLATFORM_MAX_PATH];
@ -73,12 +85,27 @@ CExtension::CExtension(const char *filename, char *error, size_t maxlength)
g_SourceMod.BuildPath(Path_SM, path, PLATFORM_MAX_PATH, "extensions/%s", filename);
}
m_Path.assign(path);
m_pLib = NULL;
Initialize(filename, path);
}
bool CExtension::Load(char *error, size_t maxlength)
bool CRemoteExtension::Load(char *error, size_t maxlength)
{
if (!PerformAPICheck(error, maxlength))
{
m_pAPI = NULL;
return false;
}
if (!CExtension::Load(error, maxlength))
{
m_pAPI = NULL;
return false;
}
return true;
}
bool CLocalExtension::Load(char *error, size_t maxlength)
{
m_pLib = g_LibSys.OpenLibrary(m_Path.c_str(), error, maxlength);
@ -99,23 +126,24 @@ bool CExtension::Load(char *error, size_t maxlength)
}
m_pAPI = pfnGetAPI();
if (!m_pAPI || m_pAPI->GetExtensionVersion() > SMINTERFACE_EXTENSIONAPI_VERSION)
/* Check pointer and version */
if (!PerformAPICheck(error, maxlength))
{
m_pLib->CloseLibrary();
m_pLib = NULL;
snprintf(error, maxlength, "Extension version is too new to load (%d, max is %d)", m_pAPI->GetExtensionVersion(), SMINTERFACE_EXTENSIONAPI_VERSION);
m_pAPI = NULL;
return false;
}
/* Load as MM:S */
if (m_pAPI->IsMetamodExtension())
{
bool already;
m_PlId = g_pMMPlugins->Load(m_Path.c_str(), g_PLID, already, error, maxlength);
}
m_pIdentToken = g_ShareSys.CreateIdentity(g_ExtType, this);
if (!m_pAPI->OnExtensionLoad(this, &g_ShareSys, error, maxlength, !g_SourceMod.IsMapLoading()))
if (!CExtension::Load(error, maxlength))
{
if (m_pAPI->IsMetamodExtension())
{
@ -126,13 +154,71 @@ bool CExtension::Load(char *error, size_t maxlength)
m_PlId = 0;
}
}
m_pAPI = NULL;
m_pLib->CloseLibrary();
m_pLib = NULL;
g_ShareSys.DestroyIdentity(m_pIdentToken);
m_pIdentToken = NULL;
m_pAPI = NULL;
return false;
} else {
}
return true;
}
void CRemoteExtension::Unload()
{
}
void CLocalExtension::Unload()
{
if (m_pAPI != NULL && m_PlId)
{
char temp_buffer[255];
g_pMMPlugins->Unload(m_PlId, true, temp_buffer, sizeof(temp_buffer));
m_PlId = 0;
}
if (m_pLib != NULL)
{
m_pLib->CloseLibrary();
m_pLib = NULL;
}
}
bool CRemoteExtension::IsExternal()
{
return true;
}
bool CLocalExtension::IsExternal()
{
return false;
}
CExtension::~CExtension()
{
DestroyIdentity();
}
bool CExtension::PerformAPICheck(char *error, size_t maxlength)
{
if (!m_pAPI || m_pAPI->GetExtensionVersion() > SMINTERFACE_EXTENSIONAPI_VERSION)
{
snprintf(error, maxlength, "Extension version is too new to load (%d, max is %d)", m_pAPI->GetExtensionVersion(), SMINTERFACE_EXTENSIONAPI_VERSION);
return false;
}
return true;
}
bool CExtension::Load(char *error, size_t maxlength)
{
CreateIdentity();
if (!m_pAPI->OnExtensionLoad(this, &g_ShareSys, error, maxlength, !g_SourceMod.IsMapLoading()))
{
DestroyIdentity();
return false;
}
else
{
/* Check if we're past load time */
if (!g_SourceMod.IsMapLoading())
{
@ -143,35 +229,33 @@ bool CExtension::Load(char *error, size_t maxlength)
return true;
}
CExtension::~CExtension()
void CExtension::CreateIdentity()
{
char temp_buffer[255];
if (m_pAPI)
if (m_pIdentToken != NULL)
{
if (m_PlId)
{
g_pMMPlugins->Unload(m_PlId, true, temp_buffer, sizeof(temp_buffer));
}
return;
}
if (m_pIdentToken)
m_pIdentToken = g_ShareSys.CreateIdentity(g_ExtType, this);
}
void CExtension::DestroyIdentity()
{
if (m_pIdentToken == NULL)
{
g_ShareSys.DestroyIdentity(m_pIdentToken);
return;
}
if (m_pLib)
{
m_pLib->CloseLibrary();
}
g_ShareSys.DestroyIdentity(m_pIdentToken);
m_pIdentToken = NULL;
}
void CExtension::MarkAllLoaded()
{
assert(m_FullyLoaded == false);
if (!m_FullyLoaded)
assert(m_bFullyLoaded == false);
if (!m_bFullyLoaded)
{
m_FullyLoaded = true;
m_bFullyLoaded = true;
m_pAPI->OnExtensionsAllLoaded();
}
}
@ -221,7 +305,12 @@ IdentityToken_t *CExtension::GetIdentity()
return m_pIdentToken;
}
bool CExtension::IsLoaded()
bool CRemoteExtension::IsLoaded()
{
return (m_pAPI != NULL);
}
bool CLocalExtension::IsLoaded()
{
return (m_pLib != NULL);
}
@ -435,7 +524,7 @@ IExtension *CExtensionManager::LoadAutoExtension(const char *path)
}
char error[256];
CExtension *p = new CExtension(path, error, sizeof(error));
CExtension *p = new CLocalExtension(path);
/* We put us in the list beforehand so extensions that check for each other
* won't recursively load each other.
@ -456,13 +545,6 @@ IExtension *CExtensionManager::FindExtensionByFile(const char *file)
List<CExtension *>::iterator iter;
CExtension *pExt;
if (!strstr(file, "." PLATFORM_LIB_EXT))
{
char path[PLATFORM_MAX_PATH];
UTIL_Format(path, sizeof(path), "%s.%s", file, PLATFORM_LIB_EXT);
return FindExtensionByFile(path);
}
/* Chomp off the path */
char lookup[PLATFORM_MAX_PATH];
g_LibSys.GetFileFromPath(lookup, sizeof(lookup), file);
@ -478,6 +560,16 @@ IExtension *CExtensionManager::FindExtensionByFile(const char *file)
}
}
/* If we got no results, test if there was a platform extension.
* If not, add one.
*/
if (!strstr(file, "." PLATFORM_LIB_EXT))
{
char path[PLATFORM_MAX_PATH];
UTIL_Format(path, sizeof(path), "%s.%s", file, PLATFORM_LIB_EXT);
return FindExtensionByFile(path);
}
return NULL;
}
@ -513,7 +605,7 @@ IExtension *CExtensionManager::FindExtensionByName(const char *ext)
return NULL;
}
IExtension *CExtensionManager::LoadExtension(const char *file, ExtensionLifetime lifetime, char *error, size_t maxlength)
IExtension *CExtensionManager::LoadExtension(const char *file, char *error, size_t maxlength)
{
IExtension *pAlready;
if ((pAlready=FindExtensionByFile(file)) != NULL)
@ -521,12 +613,11 @@ IExtension *CExtensionManager::LoadExtension(const char *file, ExtensionLifetime
return pAlready;
}
CExtension *pExt = new CExtension(file, error, maxlength);
/* :NOTE: lifetime is currently ignored */
CExtension *pExt = new CLocalExtension(file);
if (!pExt->Load(error, maxlength) || !pExt->IsLoaded())
{
pExt->Unload();
delete pExt;
return NULL;
}
@ -790,6 +881,7 @@ bool CExtensionManager::UnloadExtension(IExtension *_pExt)
}
}
pExt->Unload();
delete pExt;
List<CExtension *>::iterator iter;
@ -833,7 +925,7 @@ void CExtensionManager::MarkAllLoaded()
{
continue;
}
if (pExt->m_FullyLoaded)
if (pExt->m_bFullyLoaded)
{
continue;
}
@ -891,7 +983,9 @@ void CExtensionManager::OnRootConsoleCommand(const char *cmdname, const CCommand
if (!pExt->IsRunning(error, sizeof(error)))
{
g_RootMenu.ConsolePrint("[%02d] <FAILED> file \"%s\": %s", num, pExt->GetFilename(), error);
} else {
}
else
{
IExtensionInterface *pAPI = pExt->GetAPI();
const char *name = pAPI->GetExtensionName();
const char *version = pAPI->GetExtensionVerString();
@ -903,7 +997,9 @@ void CExtensionManager::OnRootConsoleCommand(const char *cmdname, const CCommand
}
}
return;
} else if (strcmp(cmd, "info") == 0) {
}
else if (strcmp(cmd, "info") == 0)
{
if (argcount < 4)
{
g_RootMenu.ConsolePrint("[SM] Usage: sm info <#>");
@ -952,25 +1048,43 @@ void CExtensionManager::OnRootConsoleCommand(const char *cmdname, const CCommand
{
g_RootMenu.ConsolePrint(" File: %s", pExt->GetFilename());
g_RootMenu.ConsolePrint(" Loaded: No (%s)", pExt->m_Error.c_str());
} else {
}
else
{
char error[255];
if (!pExt->IsRunning(error, sizeof(error)))
{
g_RootMenu.ConsolePrint(" File: %s", pExt->GetFilename());
g_RootMenu.ConsolePrint(" Loaded: Yes");
g_RootMenu.ConsolePrint(" Running: No (%s)", error);
} else {
}
else
{
IExtensionInterface *pAPI = pExt->GetAPI();
g_RootMenu.ConsolePrint(" File: %s", pExt->GetFilename());
g_RootMenu.ConsolePrint(" Loaded: Yes (version %s)", pAPI->GetExtensionVerString());
g_RootMenu.ConsolePrint(" Name: %s (%s)", pAPI->GetExtensionName(), pAPI->GetExtensionDescription());
g_RootMenu.ConsolePrint(" Author: %s (%s)", pAPI->GetExtensionAuthor(), pAPI->GetExtensionURL());
g_RootMenu.ConsolePrint(" Binary info: API version %d (compiled %s)", pAPI->GetExtensionVersion(), pAPI->GetExtensionDateString());
g_RootMenu.ConsolePrint(" Metamod enabled: %s", pAPI->IsMetamodExtension() ? "yes" : "no");
if (pExt->IsExternal())
{
g_RootMenu.ConsolePrint(" Method: Loaded by Metamod:Source, attached to SourceMod");
}
else if (pAPI->IsMetamodExtension())
{
g_RootMenu.ConsolePrint(" Method: Loaded by SourceMod, attached to Metamod:Source");
}
else
{
g_RootMenu.ConsolePrint(" Method: Loaded by SourceMod");
}
}
}
return;
} else if (strcmp(cmd, "unload") == 0) {
}
else if (strcmp(cmd, "unload") == 0)
{
if (argcount < 4)
{
g_RootMenu.ConsolePrint("[SM] Usage: sm unload <#> [code]");
@ -1141,3 +1255,29 @@ bool CExtensionManager::LibraryExists(const char *library)
return false;
}
IExtension *CExtensionManager::LoadExternal(IExtensionInterface *pInterface,
const char *filepath,
const char *filename,
char *error,
size_t maxlength)
{
IExtension *pAlready;
if ((pAlready=FindExtensionByFile(filename)) != NULL)
{
return pAlready;
}
CExtension *pExt = new CRemoteExtension(pInterface, filename, filepath);
if (!pExt->Load(error, maxlength) || !pExt->IsLoaded())
{
pExt->Unload();
delete pExt;
return NULL;
}
m_Libs.push_back(pExt);
return pExt;
}

View File

@ -59,13 +59,11 @@ class CExtension : public IExtension
{
friend class CExtensionManager;
public:
CExtension(const char *filename, char *error, size_t maxlen);
~CExtension();
virtual ~CExtension();
public: //IExtension
IExtensionInterface *GetAPI();
const char *GetFilename();
IdentityToken_t *GetIdentity();
bool IsLoaded();
ITERATOR *FindFirstDependency(IExtension **pOwner, SMInterface **pInterface);
bool FindNextDependency(ITERATOR *iter, IExtension **pOwner, SMInterface **pInterface);
void FreeDependencyIterator(ITERATOR *iter);
@ -79,14 +77,20 @@ public:
void RemovePlugin(IPlugin *pPlugin);
void MarkAllLoaded();
void AddLibrary(const char *library);
private:
bool Load(char *error, size_t maxlength);
private:
public:
virtual bool Load(char *error, size_t maxlength);
virtual bool IsLoaded() =0;
virtual void Unload() =0;
protected:
void Initialize(const char *filename, const char *path);
bool PerformAPICheck(char *error, size_t maxlength);
void CreateIdentity();
void DestroyIdentity();
protected:
IdentityToken_t *m_pIdentToken;
IExtensionInterface *m_pAPI;
String m_File;
String m_Path;
ILibrary *m_pLib;
String m_Error;
List<IfaceInfo> m_Deps; /** Dependencies */
List<IfaceInfo> m_ChildDeps; /** Children who might depend on us */
@ -95,9 +99,33 @@ private:
List<const sp_nativeinfo_t *> m_Natives;
List<WeakNative> m_WeakNatives;
List<String> m_Libraries;
PluginId m_PlId;
unsigned int unload_code;
bool m_FullyLoaded;
bool m_bFullyLoaded;
};
class CLocalExtension : public CExtension
{
public:
CLocalExtension(const char *filename);
public:
bool Load(char *error, size_t maxlength);
bool IsLoaded();
void Unload();
bool IsExternal();
private:
PluginId m_PlId;
ILibrary *m_pLib;
};
class CRemoteExtension : public CExtension
{
public:
CRemoteExtension(IExtensionInterface *pAPI, const char *filename, const char *path);
public:
bool Load(char *error, size_t maxlength);
bool IsLoaded();
void Unload();
bool IsExternal();
};
class CExtensionManager :
@ -114,12 +142,16 @@ public: //SMGlobalClass
void OnSourceModShutdown();
public: //IExtensionManager
IExtension *LoadExtension(const char *path,
ExtensionLifetime lifetime,
char *error,
size_t maxlength);
bool UnloadExtension(IExtension *pExt);
IExtension *FindExtensionByFile(const char *file);
IExtension *FindExtensionByName(const char *ext);
IExtension *LoadExternal(IExtensionInterface *pInterface,
const char *filepath,
const char *filename,
char *error,
size_t maxlength);
public: //IPluginsListener
void OnPluginDestroyed(IPlugin *plugin);
public: //IRootConsoleCommand

View File

@ -65,7 +65,9 @@ namespace SourceMod
/**
* @brief Returns the filename of the extension, relative to the
* extension folder.
* extension folder. If the extension is an "external" extension,
* the file path is specified by the extension itself, and may be
* arbitrary (not real).
*
* @return A string containing the extension file name.
*/
@ -112,6 +114,15 @@ namespace SourceMod
* @return True if extension is okay, false if not okay.
*/
virtual bool IsRunning(char *error, size_t maxlength) =0;
/**
* @brief Returns whether the extension is local (from the extensions
* folder), or is from an external source (such as Metamod:Source).
*
* @return True if from an external source,
* false if local to SourceMod.
*/
virtual bool IsExternal() =0;
};
/**
@ -142,10 +153,10 @@ namespace SourceMod
* @return True if load should continue, false otherwise.
*/
virtual bool OnExtensionLoad(IExtension *me,
IShareSys *sys,
char *error,
size_t maxlength,
bool late) =0;
IShareSys *sys,
char *error,
size_t maxlength,
bool late) =0;
/**
* @brief Called when the extension is about to be unloaded.
@ -166,8 +177,10 @@ namespace SourceMod
virtual void OnExtensionPauseChange(bool pause) =0;
/**
* @brief Asks the extension whether it's safe to remove an external interface it's using.
* If it's not safe, return false, and the extension will be unloaded afterwards.
* @brief Asks the extension whether it's safe to remove an external
* interface it's using. If it's not safe, return false, and the
* extension will be unloaded afterwards.
*
* NOTE: It is important to also hook NotifyInterfaceDrop() in order to clean up resources.
*
* @param pInterface Pointer to interface being dropped.
@ -199,27 +212,84 @@ namespace SourceMod
return true;
}
public:
/**
* @brief For extensions loaded through SourceMod, this should return true
* if the extension needs to attach to Metamod:Source. If the extension
* is loaded through Metamod:Source, and uses SourceMod optionally, it must
* return false.
*
* @return True if Metamod:Source is needed.
*/
virtual bool IsMetamodExtension() =0;
/**
* @brief Must return a string containing the extension's short name.
*
* @return String containing extension name.
*/
virtual const char *GetExtensionName() =0;
/**
* @brief Must return a string containing the extension's URL.
*
* @return String containing extension URL.
*/
virtual const char *GetExtensionURL() =0;
/**
* @brief Must return a string containing a short identifier tag.
*
* @return String containing extension tag.
*/
virtual const char *GetExtensionTag() =0;
/**
* @brief Must return a string containing a short author identifier.
*
* @return String containing extension author.
*/
virtual const char *GetExtensionAuthor() =0;
/**
* @brief Must return a string containing version information.
*
* Any version string format can be used, however, SourceMod
* makes a special guarantee version numbers in the form of
* A.B.C.D will always be fully displayed, where:
*
* A is a major version number of at most one digit.
* B is a minor version number of at most two digits.
* C is a minor version number of at most two digits.
* D is a build number of at most 5 digits.
*
* Thus, thirteen characters of display is guaranteed.
*
* @return String containing extension version.
*/
virtual const char *GetExtensionVerString() =0;
/**
* @brief Must return a string containing description text.
*
* The description text may be longer than the other identifiers,
* as it is only displayed when viewing one extension at a time.
* However, it should not have newlines, or any other characters
* which would otherwise disrupt the display pattern.
*
* @return String containing extension description.
*/
virtual const char *GetExtensionDescription() =0;
/**
* @brief Must return a string containing the compilation date.
*
* @return String containing the compilation date.
*/
virtual const char *GetExtensionDateString() =0;
};
#define SMINTERFACE_EXTENSIONMANAGER_NAME "IExtensionManager"
#define SMINTERFACE_EXTENSIONMANAGER_VERSION 1
/**
* @brief Not currently used.
*/
enum ExtensionLifetime
{
ExtLifetime_Forever, //Extension will never be unloaded automatically
ExtLifetime_Map, //Extension will be unloaded at the end of the map
};
#define SMINTERFACE_EXTENSIONMANAGER_VERSION 2
/**
* @brief Manages the loading/unloading of extensions.
@ -235,26 +305,66 @@ namespace SourceMod
{
return SMINTERFACE_EXTENSIONMANAGER_VERSION;
}
virtual bool IsVersionCompatible(unsigned int version)
{
if (version < 2)
{
return false;
}
return SMInterface::IsVersionCompatible(version);
}
public:
/**
* @brief Loads a extension into the extension system.
*
* @param path Path to extension file, relative to the extensions folder.
* @param lifetime Lifetime of the extension (currently ignored).
* @param error Error buffer.
* @param maxlength Maximum error buffer length.
* @return New IExtension on success, NULL on failure.
* @param path Path to extension file, relative to the
* extensions folder.
* @param lifetime Lifetime of the extension (currently ignored).
* @param error Error buffer.
* @param maxlength Maximum error buffer length.
* @return New IExtension on success, NULL on failure.
* If NULL is returned, the error buffer will be
* filled with a null-terminated string.
*/
virtual IExtension *LoadExtension(const char *path,
ExtensionLifetime lifetime,
char *error,
size_t maxlength) =0;
char *error,
size_t maxlength) =0;
/**
* @brief Attempts to unload a module.
* @brief Loads an extension into the extension system, directly,
* as an external extension.
*
* @param pExt IExtension pointer.
* @return True if successful, false otherwise.
* The extension receives all normal callbacks. However, it is
* never opened via LoadLibrary/dlopen or closed via FreeLibrary
* or dlclose.
*
* @param pInterface Pointer to an IExtensionInterface instance.
* @param filepath Absolute path to the extension's file.
* @param filename Name to use to uniquely identify the extension.
* The name should be generic, without any
* platform-specific suffices. For example,
* sdktools.ext instead of sdktools.ext.so.
* This filename is used to detect if the
* extension is already loaded, and to verify
* plugins that require the same extension.
* @param error Buffer to store error message.
* @param maxlength Maximum size of the error buffer.
* @return IExtension pointer on success, NULL on failure.
* If NULL is returned, the error buffer will be
* filled with a null-terminated string.
*/
virtual IExtension *LoadExternal(IExtensionInterface *pInterface,
const char *filepath,
const char *filename,
char *error,
size_t maxlength) =0;
/**
* @brief Attempts to unload an extension. External extensions must
* call this before unloading.
*
* @param pExt IExtension pointer.
* @return True if successful, false otherwise.
*/
virtual bool UnloadExtension(IExtension *pExt) =0;
};