Compare commits

..

4 Commits

Author SHA1 Message Date
caxanga334
e567441f7a
Update Example Plugin
Removed timer example.
Added example of how to handle plugin late loads.
2025-11-24 16:43:14 -03:00
caxanga334
68ba00ff46
Fix Error 2025-11-24 11:56:40 -03:00
caxanga334
d06a0fddc6
Implement Requested Changes 2025-11-24 11:54:46 -03:00
caxanga334
1a6223ad82
Refactor API
Forward notifies plugins when the extension is done uploading.
Plugins can fetch data from uploaded crashes via natives.
Added example plugin.
2025-11-24 11:12:05 -03:00
11 changed files with 358 additions and 77 deletions

View File

@ -10,6 +10,7 @@ folder_list = [
'addons/sourcemod/extensions',
'addons/sourcemod/gamedata',
'addons/sourcemod/configs',
'addons/sourcemod/scripting',
'addons/sourcemod/scripting/include',
]
@ -44,12 +45,19 @@ def CopyDirContent(src, dest):
builder.AddCopy(item.path, dest_entry)
# Include files
CopyFiles('scripting', 'addons/sourcemod/scripting/include',
CopyFiles('scripting/include', 'addons/sourcemod/scripting/include',
[
'accelerator.inc',
]
)
# Example files
CopyFiles('scripting', 'addons/sourcemod/scripting',
[
'accelerator_example.sp',
]
)
# Copy binaries.
for task in Accelerator.extension:
if task.target.arch == 'x86_64':

View File

@ -8,6 +8,7 @@ project.sources = [
'extension.cpp',
'MemoryDownloader.cpp',
'forwards.cpp',
'natives.cpp',
os.path.join(Accelerator.sm_root, 'public', 'smsdk_ext.cpp')
]

View File

@ -28,6 +28,7 @@
#include <IWebternet.h>
#include "MemoryDownloader.h"
#include "forwards.h"
#include "natives.h"
#if defined _LINUX
#include "client/linux/handler/exception_handler.h"
@ -465,7 +466,8 @@ class UploadThread: public IThread
count++;
g_pSM->LogError(myself, "Accelerator uploaded crash dump: %s", response);
if (log) fprintf(log, "Uploaded crash dump: %s\n", response);
extforwards::CallForward(count, response);
UploadedCrash crash{ response };
g_accelerator.StoreUploadedCrash(crash);
} else {
failed++;
g_pSM->LogError(myself, "Accelerator failed to upload crash dump: %s", response);
@ -497,6 +499,7 @@ class UploadThread: public IThread
log = nullptr;
}
g_accelerator.MarkAsDoneUploading();
rootconsole->ConsolePrint("Accelerator upload thread finished. (%d skipped, %d uploaded, %d failed)", skip, count, failed);
}
@ -1082,6 +1085,25 @@ class UploadThread: public IThread
}
} uploadThread;
class SourcePawnNotifyThread : public IThread
{
public:
void RunThread(IThreadHandle* pHandle) {
for (;;) {
// Wait until OnMapStart is called once, this should be enough delay to make sure plugins are loaded.
if (g_accelerator.IsMapStarted() && g_accelerator.IsDoneUploading()) {
extforwards::CallOnDoneUploadingForward();
break;
}
}
}
void OnTerminate(IThreadHandle* pHandle, bool cancel) {
}
} spNotifyThread;
class VFuncEmptyClass {};
const char *GetCmdLine()
@ -1119,6 +1141,11 @@ const char *GetCmdLine()
return (const char *)(reinterpret_cast<VFuncEmptyClass*>(cmdline)->*u.mfpnew)();
}
Accelerator::Accelerator() :
m_doneuploading(false), m_maphasstarted(false)
{
}
bool Accelerator::SDK_OnLoad(char *error, size_t maxlength, bool late)
{
sharesys->AddDependency(myself, "webternet.ext", true, true);
@ -1144,6 +1171,7 @@ bool Accelerator::SDK_OnLoad(char *error, size_t maxlength, bool late)
strncpy(crashGameDirectory, g_pSM->GetGameFolderName(), sizeof(crashGameDirectory) - 1);
threader->MakeThread(&uploadThread);
threader->MakeThread(&spNotifyThread); // This thread waits for accelator to be done uploading and for the first OnMapStart call, then fires a SourceMod forward
do {
char gameconfigError[256];
@ -1343,11 +1371,17 @@ void Accelerator::SDK_OnAllLoaded()
{
extforwards::Init();
sharesys->RegisterLibrary(myself, "accelerator");
natives::Setup(m_natives);
m_natives.push_back({ nullptr, nullptr }); // SM requires this to signal the end of the native info array
sharesys->AddNatives(myself, m_natives.data());
}
void Accelerator::OnCoreMapStart(edict_t *pEdictList, int edictCount, int clientMax)
{
strncpy(crashMap, gamehelpers->GetCurrentMap(), sizeof(crashMap) - 1);
m_maphasstarted.store(true);
}
/* 010 Editor Template
@ -1508,3 +1542,25 @@ void Accelerator::OnPluginUnloaded(IPlugin *plugin)
SerializePluginContexts();
}
void Accelerator::StoreUploadedCrash(UploadedCrash& crash)
{
std::lock_guard<std::mutex> lock(m_uploadedcrashes_mutex);
m_uploadedcrashes.push_back(std::move(crash));
}
const UploadedCrash* Accelerator::GetUploadedCrash(int element) const
{
std::lock_guard<std::mutex> lock(m_uploadedcrashes_mutex);
if (element < 0 || element >= static_cast<int>(m_uploadedcrashes.size())) {
return nullptr;
}
return &m_uploadedcrashes[element];
}
cell_t Accelerator::GetUploadedCrashCount() const
{
std::lock_guard<std::mutex> lock(m_uploadedcrashes_mutex);
return static_cast<cell_t>(m_uploadedcrashes.size());
}

View File

@ -26,14 +26,36 @@
* @brief Accelerator extension code header.
*/
#include <atomic>
#include <vector>
#include <mutex>
#include "smsdk_ext.h"
/**
* @brief Represents a crash that has been successfully uploaded to Accelerator's backend
*/
class UploadedCrash
{
public:
UploadedCrash(const char* response) :
m_httpresponse(response)
{
}
const std::string& GetHTTPResponse() const { return m_httpresponse; }
private:
std::string m_httpresponse; // HTTP response from the crash upload in free form text.
};
/**
* @brief Sample implementation of the SDK Extension.
*/
class Accelerator : public SDKExtension, IPluginsListener
{
public: // SDKExtension
Accelerator();
/**
* @brief This is called after the initial loading sequence has been processed.
*
@ -73,6 +95,47 @@ public: // IPluginsListener
* @brief Called when a plugin is unloaded (only if fully loaded).
*/
virtual void OnPluginUnloaded(IPlugin *plugin);
/**
* @brief Stores an uploaded crash into the vector.
* @param crash Uploaded crash instance to store.
*/
void StoreUploadedCrash(UploadedCrash& crash);
/**
* @brief Retrieves an uploaded crash from the given element.
* @param element Vector element (index) to read.
* @return Upload crash if found or NULL if out of bounds.
*/
const UploadedCrash* GetUploadedCrash(int element) const;
/**
* @brief Returns the number of uploaded crashes (for SourcePawn plugins).
* @return Number of uploaded crashes.
*/
cell_t GetUploadedCrashCount() const;
/**
* @brief Signals the extension that uploading is done.
*/
void MarkAsDoneUploading() { m_doneuploading.store(true); }
/**
* @brief Is Accelerator done uploading crashes.
* @return Returns true if yes, false otherwise.
*/
bool IsDoneUploading() const { return m_doneuploading.load(); }
/**
* @brief Has the 'OnMapStart' function called at least once.
* @return True if yes, false otherwise.
*/
bool IsMapStarted() const { return m_maphasstarted.load(); }
private:
std::vector<UploadedCrash> m_uploadedcrashes; // Vector of uploaded crashes
std::vector<sp_nativeinfo_t> m_natives; // Vector of SourcePawn natives
mutable std::mutex m_uploadedcrashes_mutex; // mutex for accessing the m_uploadedcrashes vector
std::atomic_bool m_doneuploading; // Signals that Accelerator is done uploading crashes.
std::atomic_bool m_maphasstarted; // Signals that OnMapStart has been called at least once.
};
// Expose the extension singleton.
extern Accelerator g_accelerator;
#endif // _INCLUDE_SOURCEMOD_EXTENSION_PROPER_H_

View File

@ -3,57 +3,34 @@
#include "extension.h"
#include "forwards.h"
static SourceMod::IForward* s_postuploadfoward = nullptr;
struct CrashPawnData
static SourceMod::IForward* s_ondoneuploadingforward = nullptr;
static void OnDoneUploadingCallback(void* data)
{
int index;
std::string response;
};
static void OnCrashUploadedCallback(void* data)
{
CrashPawnData* crashdata = reinterpret_cast<CrashPawnData*>(data);
if (s_postuploadfoward)
{
s_postuploadfoward->PushCell(crashdata->index);
s_postuploadfoward->PushString(crashdata->response.c_str());
s_postuploadfoward->PushCell(static_cast<cell_t>(crashdata->response.size()));
s_postuploadfoward->Execute(nullptr);
if (s_ondoneuploadingforward) {
s_ondoneuploadingforward->Execute();
}
delete crashdata;
}
void extforwards::Init()
{
std::array types = {
SourceMod::ParamType::Param_Cell,
SourceMod::ParamType::Param_String,
SourceMod::ParamType::Param_Cell,
};
s_postuploadfoward = forwards->CreateForward("Accelerator_OnCrashUploaded", SourceMod::ExecType::ET_Ignore, 3, types.data());
s_ondoneuploadingforward = forwards->CreateForward("Accelerator_OnDoneUploadingCrashes", SourceMod::ExecType::ET_Ignore, 0, nullptr);
}
void extforwards::Shutdown()
{
if (s_postuploadfoward)
{
forwards->ReleaseForward(s_postuploadfoward);
s_postuploadfoward = nullptr;
if (s_ondoneuploadingforward) {
forwards->ReleaseForward(s_ondoneuploadingforward);
s_ondoneuploadingforward = nullptr;
}
}
void extforwards::CallForward(int crashIndex, const char* response)
void extforwards::CallOnDoneUploadingForward()
{
CrashPawnData* data = new CrashPawnData;
data->index = crashIndex;
data->response.assign(response);
// Accelerator uploads from a secondary thread, calling a forward directly will crash
// this will call the forward from the main thread.
smutils->AddFrameAction(OnCrashUploadedCallback, reinterpret_cast<void*>(data));
// It's obligatory to use AddFrameAction here because SourcePawn forwards can only be called from the server's main thread.
// AddFrameAction will do this for us and is also thread safe.
smutils->AddFrameAction(OnDoneUploadingCallback, nullptr);
}

View File

@ -3,9 +3,14 @@
namespace extforwards
{
// Initialize the sourcepawn forwards.
void Init();
// Shutdown the sourcepawn forwards.
void Shutdown();
void CallForward(int crashIndex, const char* response);
// Calls the on done uploadind forward. (thread safe)
void CallOnDoneUploadingForward();
}
#endif // !_INCLUDE_FORWARDS_H_

54
extension/natives.cpp Normal file
View File

@ -0,0 +1,54 @@
#include <algorithm>
#include <amtl/am-string.h>
#include "extension.h"
#include "natives.h"
static cell_t Native_GetUploadedCrashCount(IPluginContext* context, const cell_t* params)
{
return g_accelerator.GetUploadedCrashCount();
}
static cell_t Native_IsDoneUploadingCrashes(IPluginContext* context, const cell_t* params)
{
if (g_accelerator.IsDoneUploading()) {
return 1;
}
return 0;
}
static cell_t Native_GetCrashHTTPResponse(IPluginContext* context, const cell_t* params)
{
if (!g_accelerator.IsDoneUploading()) {
context->ReportError("Wait until accelerator is done uploading crashes before accessing crash information!");
return 0;
}
int element = static_cast<int>(params[1]);
const UploadedCrash* crash = g_accelerator.GetUploadedCrash(element);
if (!crash) {
context->ReportError("Crash index %i is invalid!", element);
return 0;
}
char* buffer;
context->LocalToString(params[2], &buffer);
size_t maxsize = static_cast<size_t>(params[3]);
ke::SafeStrcpy(buffer, maxsize, crash->GetHTTPResponse().c_str());
return 0;
}
void natives::Setup(std::vector<sp_nativeinfo_t>& vec)
{
sp_nativeinfo_t list[] = {
{"Accelerator_GetUploadedCrashCount", Native_GetUploadedCrashCount},
{"Accelerator_IsDoneUploadingCrashes", Native_IsDoneUploadingCrashes},
{"Accelerator_GetCrashHTTPResponse", Native_GetCrashHTTPResponse},
};
vec.insert(vec.end(), std::begin(list), std::end(list));
}

11
extension/natives.h Normal file
View File

@ -0,0 +1,11 @@
#ifndef _INCLUDE_NATIVES_H_
#define _INCLUDE_NATIVES_H_
namespace natives
{
// Called during the load process to setup the extension natives
void Setup(std::vector<sp_nativeinfo_t>& vec);
}
#endif // !_INCLUDE_NATIVES_H_

View File

@ -1,37 +0,0 @@
/**
* Accelerator's SourcePawn include file
*/
#if defined _accelerator_included
#endinput
#endif
#define _accelerator_included
/**
* Called when a crash dump is uploaded.
*
* @param index Index of the crash.
* @param response HTTP response in plain text.
* @param responsesize HTTP response char buffer size.
*/
forward void Accelerator_OnCrashUploaded(int index, const char[] response, int responsesize);
/**
* Do not edit below this line!
*/
public Extension __ext_accelerator =
{
name = "accelerator",
file = "accelerator.ext",
#if defined AUTOLOAD_EXTENSIONS
autoload = 1,
#else
autoload = 0,
#endif
#if defined REQUIRE_EXTENSIONS
required = 1,
#else
required = 0,
#endif
};

View File

@ -0,0 +1,76 @@
#include <sourcemod>
#include <accelerator>
#pragma newdecls required
#pragma semicolon 1
public Plugin myinfo =
{
name = "Accelerator Example",
author = "caxanga334",
description = "Example Accelerator natives plugin.",
version = "1.0.0",
url = "https://github.com/asherkin/accelerator"
};
public void OnPluginStart()
{
RegAdminCmd("sm_listcrashes", Command_ListCrashes, ADMFLAG_RCON, "List all uploaded crash dumps.");
// Call the forward manually. (For late loading plugins)
if (Accelerator_IsDoneUploadingCrashes())
{
Accelerator_OnDoneUploadingCrashes();
}
}
// This is called when the extension is done uploading crashes
public void Accelerator_OnDoneUploadingCrashes()
{
LogMessage("Accelerator is done uploading crashes!");
int max = Accelerator_GetUploadedCrashCount();
if (max == 0)
{
LogMessage("No crashes were uploaded!");
return;
}
char buffer[512];
for (int i = 0; i < max; i++)
{
Accelerator_GetCrashHTTPResponse(i, buffer, sizeof(buffer));
LogMessage("Crash #%i: HTTP reponse: \"%s\".", i, buffer);
}
}
// Admin command to list crashes
Action Command_ListCrashes(int client, int args)
{
if (!Accelerator_IsDoneUploadingCrashes())
{
ReplyToCommand(client, "Accelerator is still uploading crashes, please wait!");
return Plugin_Handled;
}
int max = Accelerator_GetUploadedCrashCount();
if (max == 0)
{
ReplyToCommand(client, "No crashes were uploaded!");
return Plugin_Handled;
}
char buffer[512];
for (int i = 0; i < max; i++)
{
Accelerator_GetCrashHTTPResponse(i, buffer, sizeof(buffer));
ReplyToCommand(client, "Crash #%i: HTTP reponse: \"%s\".", i, buffer);
}
return Plugin_Handled;
}

View File

@ -0,0 +1,67 @@
/**
* Accelerator's SourcePawn include file
*/
#if defined _accelerator_included
#endinput
#endif
#define _accelerator_included
/**
* Called when Accelerator is done uploading crash dumps.
*/
forward void Accelerator_OnDoneUploadingCrashes();
/**
* Returns the number of crashes uploaded.
*
* @return Number of crashes uploaded.
*/
native int Accelerator_GetUploadedCrashCount();
/**
* Returns if Accelerator is done uploading crashes.
*
* @return True if no more crashes will be uploaded, false if there are still crashes pending upload.
*/
native bool Accelerator_IsDoneUploadingCrashes();
/**
* Gets a crash's HTTP response from Accelerator's backend server.
*
* @note The response from the server can be anything and there is no guarantee a crash ID will be present.
* @param index The crash index, starts at 0 and goes up to Accelerator_GetUploadedCrashCount() - 1;
* @param buffer Buffer to store the crash HTTP response.
* @param size Size of the buffer parameter.
* @error Native called before Accelerator_IsDoneUploadingCrashes() returns true or invalid crash index passed.
*/
native void Accelerator_GetCrashHTTPResponse(int index, char[] buffer, int size);
/**
* Do not edit below this line!
*/
public Extension __ext_accelerator =
{
name = "accelerator",
file = "accelerator.ext",
#if defined AUTOLOAD_EXTENSIONS
autoload = 1,
#else
autoload = 0,
#endif
#if defined REQUIRE_EXTENSIONS
required = 1,
#else
required = 0,
#endif
};
#if !defined REQUIRE_EXTENSIONS
public __ext_accelerator_SetNTVOptional()
{
MarkNativeAsOptional("Accelerator_GetUploadedCrashCount");
MarkNativeAsOptional("Accelerator_IsDoneUploadingCrashes");
MarkNativeAsOptional("Accelerator_GetCrashHTTPResponse");
}
#endif