public void Event_RoundStart(Event event, const char[] name, bool dontBroadcast)
{
delete g_iTimer; // delete previous timer in case "round_start" fired twice
g_iTimer = CreateTimer(30.0, Timer_Sample);
// do not put here TIMER_FLAG_NO_MAPCHANGE !!!
// because handle's variable will not be zeroed automatically in case map change happens before timer is fired
}
public void Event_RoundEnd(Event event, const char[] name, bool dontBroadcast)
{
delete g_iTimer; // stop timer if it is not fired yet, so this timer will not fire on the next round or between level load
// "delete" keyword stops the timer and automatically assign null with variable.
// Notice: delete 0 will not raise an exception.
}
public void OnMapEnd() // required, because forcible change level doesn't fire "round_end" event
{
delete g_iTimer;
}
public Action Timer_Sample(Handle timer)
{
//do something
PrintToChatAll("tick");
// do not forget to zero timer's handle here, because after callback finishes, timer's handle become invalid
g_iTimer = null;
}
public void Event_RoundStart(Event event, const char[] name, bool dontBroadcast)
{
// let's start repeatable timer on each round start
delete g_iTimer; // prevent double-run (just in case)
// do not put here TIMER_FLAG_NO_MAPCHANGE !!!
// because handle variable will not be zeroed
g_iTimer = CreateTimer(1.0, Timer_Sample, _, TIMER_REPEAT);
}
public void Event_RoundEnd(Event event, const char[] name, bool dontBroadcast)
{
delete g_iTimer;
// "delete" keyword automatically assign null with variable.
// Notice: delete 0 will not raise an exception.
}
public void OnMapEnd() // required, because ForceChangeLevel doesn't cause "round_end" event
{
delete g_iTimer;
}
public Action Timer_Sample(Handle timer)
{
// do something
PrintToChatAll("tick");
return Plugin_Continue;
/*
Notice 1: if you want forcibly stop timer here, you have to zero handle's variable firstly
g_iTimer = null;
return Plugin_Stop;
Notice 2: Never try to delete or KillTimer() within this timer callback itself.
*/
}
3. Global single per-client timer (timer_single_per-client.sp)
Spoiler
PHP Code:
#include <sourcemod>
Handle g_iTimer[MAXPLAYERS+1];
public void OnPluginStart()
{
HookEvent("round_start", Event_RoundStart, EventHookMode_PostNoCopy);
HookEvent("round_end", Event_RoundEnd, EventHookMode_PostNoCopy);
RegConsoleCmd("sm_timer", CmdTestTimer, "Re-starts timer for testing")
}
public Action Timer_Sample(Handle timer, int UserId)
{
int client = GetClientOfUserId(UserId);
if ( client && IsClientInGame(client) )
{
g_iTimer[client] = null;
// do something
// ...
PrintToChat(client, "tick");
}
}
4. Global repeatable per-client timer (timer_repeat_per-client.sp)
Spoiler
PHP Code:
#include <sourcemod>
Handle g_iTimer[MAXPLAYERS+1];
public void OnPluginStart()
{
HookEvent("round_start", Event_RoundStart, EventHookMode_PostNoCopy);
HookEvent("round_end", Event_RoundEnd, EventHookMode_PostNoCopy);
RegConsoleCmd("sm_timer", CmdTestTimer, "Starts timer for testing")
}
Action CmdTestTimer(int client, int args)
{
// stop timer if it was already running
// this will also set the variable to zero
delete g_iTimer[client];
PrintToChatAll("timer = %i", g_iTimer[client]);
// starts repeatable timer
// pass reference number of client, so we can ensure it is not replaced when timer will actually fire
g_iTimer[client] = CreateTimer(15.0, Timer_Sample, GetClientUserId(client), TIMER_REPEAT);
}
public void Event_RoundStart(Event event, const char[] name, bool dontBroadcast)
{
for ( int i = 1; i <= MaxClients; i++ )
{
if ( IsClientInGame(i) )
{
// start single per-client timer
delete g_iTimer[i];
g_iTimer[i] = CreateTimer(15.0, Timer_Sample, GetClientUserId(i), TIMER_REPEAT);
}
}
}
public void OnClientDisconnect(int client)
{
// ensure, you kill timer when player disconnects
delete g_iTimer[client];
}
void Reset()
{
for ( int i = 0; i <= MaxClients; i++ )
{
delete g_iTimer[i]; // stops timer for each player
}
}
public void OnMapEnd() // required, because ForceChangeLevel doesn't cause "round_end" event
{
Reset();
}
public Action Timer_Sample(Handle timer, int UserId)
{
// check is it the same client
int client = GetClientOfUserId(UserId);
// ensure it is valid
if ( client && IsClientInGame(client) )
{
// do something
// ...
PrintToChat(client, "tick");
return Plugin_Continue;
/* or, if you want to Stop timer,
don't forget to 'null' timer firstly:
g_iTimer[client] = null;
return Plugin_Stop;
*/
}
// here you can't null timer, because you don't know correct client index for accessing handle's variable,
// so OnClientDisconnect() forward is used instead
return Plugin_Stop;
}
5. (No global var.) Pause repeatable timer
- without the need to manage handle in global variable (timer_repeat_pause.sp)
Spoiler
PHP Code:
/*
This sample demonstrates 'Slapping player' after 30.0 sec delay on round start and repeat each 2 seconds until round ends.
Thanks to Marttt for idea and code.
*/
#pragma semicolon 1
#pragma newdecls required
#include <sourcemod>
#include <sdktools>
#define WAIT_TIME 30.0
float g_fRoundStartTime;
public void OnPluginStart()
{
HookEvent("round_start", Event_RoundStart, EventHookMode_PostNoCopy);
HookEvent("round_end", Event_RoundEnd, EventHookMode_PostNoCopy);
// starts the timer that will never be destroed
CreateTimer(2.0, Timer_CheckPlayerZone, _, TIMER_REPEAT);
}
public void OnMapEnd()
{
g_fRoundStartTime = 0.0;
}
public Action Timer_CheckPlayerZone(Handle timer)
{
if ( g_fRoundStartTime == 0.0 ) // pause timer between "round_end" - "round_start" events
return Plugin_Continue;
if ( GetEngineTime() - g_fRoundStartTime < WAIT_TIME ) // pause timer within 30.0 sec interval after "round_start"
return Plugin_Continue;
for ( int i = 1; i <= MaxClients; i++ )
{
if ( IsClientInGame(i) && IsPlayerAlive(i) )
{
SlapPlayer(i, 20, true);
PrintToChat(i, "You are being slapped!");
}
}
return Plugin_Continue;
}
-------------------
General tips:
Do not use TIMER_FLAG_NO_MAPCHANGE flag if you assign timer's handle to a global variable to control the timer via this variable later.
- TIMER_FLAG_NO_MAPCHANGE flag can be safely used if you create timer without assigning its handle to a variable.
When you kill timer outside the timer's callback, use 'delete' keyword.
When you kill repeatable timer within its own callback, use "return Plugin_Stop" only!
- Do not use "delete" or "KillTimer()" there, otherwise you might receive error:
Quote:
Plugin "XXX.smx" encountered error 23: Native detected error
[SM] Invalid timer handle XXX (error 3) during timer end, displayed function is timer callback, not the stack trace
You should always assign zero to timer's global variable in the very last line of timer's callback (before return) if timer's handle is about to destruct, that happen:
- for repeatable timer, when you pass "return Plugin_Stop"
- for single time, automatically when callback finishes.
When your global variable is array-based and you don't know how to find it within timer's callback:
- with per-client timers, use OnClientDisconnect() forward to do it.
- with incremental based global timers:
you could add the multithread functions recommendation for SQL
There are actually no so-called multi-threaded functions.
There are only:
non-threaded (executed in the same thread as sm plugins, so each query to db cause waiting the response and pausing other plugin's operations during this time).
threaded (this one executes db queries in other second thread; when you execute one more query in parallel, no more threads created, it queues to the same second thread).
Quote:
Originally Posted by aleeexxx
I until recently knew that the old functions could block the main thread, the new syntax already does it in multithread
correct me if I'm wrong!
Not all of them.
Some of Database methodmap functions still point to the old non-threded functions at the moment.
There are also some exclusions.
Example: Database.Escape() - even if according to source code, it access the db handle, no external query is processed. Instead, client makes escaping itself based on pre-cached codepage of connection.
Most of above info is provided by Kruzya (CrazyHackGUT).
Thanks to him.
Additional info can be obtained in this article (in Russian) by R1KO.
Reading comments would be also useful.
in header, since tutorial is intended for novice as well.
Updates for descriptions of some errors:
Addition for: "Native XXX is not bound"
- to prevent calling a native of unloaded plugin, implement OnLibraryAdded() and OnLibraryRemoved() natives.
Do not use LibraryExists() constantly because it's a very CPU demanding operation.
Unable to load plugin (bad header) - error similar to "Illegal disk size". (maybe just append it in same sentence)
Had already noticed and fixed AddAmbientSoundHook.
Will add a clarification for this: OnLibraryAdded() and OnLibraryRemoved() can be used instead of ever calling LibraryExists() since they both trigger when late loading a plugin that's using it.
Nice details for 1.11 enum structs change, many have been asking how to fix.
I'll just leave it as information maybe it will be useful for someone
INT & BOOL & FLOAT
PHP Code:
Bench: Min 0.000016. Avg 0.000016. Max 0.000017 - g_iTest = GetConVarInt(vs_max_team_switches);
Bench: Min 0.000001. Avg 0.000001. Max 0.000002 - g_iTest = g_iConst; Bench: Min 0.000001. Avg 0.000001. Max 0.000002 - g_iTest = g_iValue;
Bench: Min 0.000001. Avg 0.000001. Max 0.000002 - g_iTest = g_flConst; Bench: Min 0.000001. Avg 0.000001. Max 0.000002 - g_iTest = g_flValue;
Bench: Min 0.000001. Avg 0.000002. Max 0.000002 - g_iTest = g_bByte; Bench: Min 0.000001. Avg 0.000002. Max 0.000002 - g_bBool = !g_bBool;
Bench: Min 0.000001. Avg 0.000002. Max 0.000002 { if (!g_bBool) g_bBool = true; else g_bBool = false; }
PROPERTY
PHP Code:
Bench: Min 0.000075. Avg 0.001785. Max 0.034192 - SetEntProp(0, Prop_Send, "m_bColdWorld", 1); Bench: Min 0.000017. Avg 0.000018. Max 0.000032 - SetEntData(0, g_iOffset, 1);
command
PHP Code:
Bench: Min 0.000006. Avg 0.000007. Max 0.000018 - sm_somecommand(client, 0); Bench: Min 0.001201. Avg 0.001476. Max 0.002383 - FakeClientCommand(client, "sm_somecommand"); Bench: Min 0.000213. Avg 0.000286. Max 0.000483 - ClientCommand(client, "sm_somecommand");
CHAR
PHP Code:
Bench: Min 0.000010. Avg 0.000011. Max 0.000012 - g_bBool = (strlen(szChar) > 0); Bench: Min 0.000001. Avg 0.000001. Max 0.000002 - g_bBool = (szChar[0] != '\0'); Bench: Min 0.000001. Avg 0.000001. Max 0.000002 - g_bBool = (szChar[0] != 0);
Bench: Min 0.000008. Avg 0.000008. Max 0.000009 - szChar = "S"; Bench: Min 0.000020. Avg 0.000024. Max 0.000057 - Format(szChar, 1, "S");
OTHER
PHP Code:
Bench: Min 0.000281. Avg 0.000476. Max 0.000763 { for( int i = 0; i < 1000; i++ ) { for (int l = 1; l <= MaxClients; l++) { if (IsClientInGame(l) && GetClientTeam(l) == 3 && IsPlayerAlive(l)) { g_bBool = true; } } } }
Bench: Min 0.000281. Avg 0.000299. Max 0.000421 { for( int i = 0; i < 1000; i++ ) { for (int l = 1; l <= MaxClients; l++) { if (!IsClientInGame(l) || GetClientTeam(l) != 3 || !IsPlayerAlive(l)) continue;
g_bBool = true; } } }
Code
PHP Code:
/* Thanks Silvers for code to profiling */
#pragma newdecls required #pragma semicolon 1
#include <sourcemod> #include <profiler>
static const int g_iConst = 66666666; int g_iValue = 66666666;
I'll just leave it as information maybe it will be useful for someone
INT & BOOL & FLOAT
PHP Code:
Bench: Min 0.000016. Avg 0.000016. Max 0.000017 - g_iTest = GetConVarInt(vs_max_team_switches);
Bench: Min 0.000001. Avg 0.000001. Max 0.000002 - g_iTest = g_iConst; Bench: Min 0.000001. Avg 0.000001. Max 0.000002 - g_iTest = g_iValue;
Bench: Min 0.000001. Avg 0.000001. Max 0.000002 - g_iTest = g_flConst; Bench: Min 0.000001. Avg 0.000001. Max 0.000002 - g_iTest = g_flValue;
Bench: Min 0.000001. Avg 0.000002. Max 0.000002 - g_iTest = g_bByte; Bench: Min 0.000001. Avg 0.000002. Max 0.000002 - g_bBool = !g_bBool;
Bench: Min 0.000001. Avg 0.000002. Max 0.000002 { if (!g_bBool) g_bBool = true; else g_bBool = false; }
PROPERTY
PHP Code:
Bench: Min 0.000075. Avg 0.001785. Max 0.034192 - SetEntProp(0, Prop_Send, "m_bColdWorld", 1); Bench: Min 0.000017. Avg 0.000018. Max 0.000032 - SetEntData(0, g_iOffset, 1);
command
PHP Code:
Bench: Min 0.000006. Avg 0.000007. Max 0.000018 - sm_somecommand(client, 0); Bench: Min 0.001201. Avg 0.001476. Max 0.002383 - FakeClientCommand(client, "sm_somecommand"); Bench: Min 0.000213. Avg 0.000286. Max 0.000483 - ClientCommand(client, "sm_somecommand");
CHAR
PHP Code:
Bench: Min 0.000010. Avg 0.000011. Max 0.000012 - g_bBool = (strlen(szChar) > 0); Bench: Min 0.000001. Avg 0.000001. Max 0.000002 - g_bBool = (szChar[0] != '\0'); Bench: Min 0.000001. Avg 0.000001. Max 0.000002 - g_bBool = (szChar[0] != 0);
Bench: Min 0.000008. Avg 0.000008. Max 0.000009 - szChar = "S"; Bench: Min 0.000020. Avg 0.000024. Max 0.000057 - Format(szChar, 1, "S");
OTHER
PHP Code:
Bench: Min 0.000281. Avg 0.000476. Max 0.000763 { for( int i = 0; i < 1000; i++ ) { for (int l = 1; l <= MaxClients; l++) { if (IsClientInGame(l) && GetClientTeam(l) == 3 && IsPlayerAlive(l)) { g_bBool = true; } } } }
Bench: Min 0.000281. Avg 0.000299. Max 0.000421 { for( int i = 0; i < 1000; i++ ) { for (int l = 1; l <= MaxClients; l++) { if (!IsClientInGame(l) || GetClientTeam(l) != 3 || !IsPlayerAlive(l)) continue;
g_bBool = true; } } }
Code
PHP Code:
/* Thanks Silvers for code to profiling */
#pragma newdecls required #pragma semicolon 1
#include <sourcemod> #include <profiler>
static const int g_iConst = 66666666; int g_iValue = 66666666;
public Action sm_somecommand (int client, int args) { }
public Action sm_test (int client, int args) { Handle hProf = CreateProfiler(); float min = 10.0, max, avg;
// Running the test 20 times to gain min/max/avg for( int x = 0; x < 20; x++ ) { StartProfiling(hProf);
// Running 1000 iterations for our test for( int i = 0; i < 1000; i++ ) {
}
StopProfiling(hProf);
float speed = GetProfilerTime(hProf); if( speed < min ) min = speed; if( speed > max ) max = speed; avg += speed; }
// Display our minimum, maximum and average times. avg /= 20; PrintToServer("Bench: Min %f. Avg %f. Max %f", min, avg, max); delete hProf; }
You should do these bench-tests without C-States skewing the results causing high variance between the VM.
Example below way too much variance.
PHP Code:
Bench: Min 0.000075. Avg 0.001785. Max 0.034192 - SetEntProp(0, Prop_Send, "m_bColdWorld", 1)
For abit of context anyone who will choose the faster function.
ClientCommand and FakeClientCommand are not comparable they do different jobs FakeClientCommandEx would be equivalent.
PHP Code:
Bench: Min 0.001201. Avg 0.001476. Max 0.002383 - FakeClientCommand(client, "sm_somecommand"); Bench: Min 0.000213. Avg 0.000286. Max 0.000483 - ClientCommand(client, "sm_somecommand");