KeyValues (SourceMod Scripting)

May 7th, 2007

Today’s article is a wiki article about KeyValues (SourceMod Scripting).

While we don’t encourage using KeyValues for C++, they work quite nicely in scripts, where event based parsers are more complicated to set up, especially since dynamic structures do not otherwise exist yet.

The original API for KeyValues was very confusing, which was a direct result of Valve’s header being poorly documented. However, the current version of the API is much more useful and covers most of the use cases as easily as possible. The wiki article goes into detail about iteration, traversal, and deletion.

The only noteworthy (and confusing) thing about SourceMod’s KeyValues API is the traversal stack. Why does it maintain an internal stack of how deep you’ve nested? Because the only other solution would be to return a new Handle. Observe the difference:

native bool:KvFindKey(Handle:kv, const String:name[]);
native Handle:KvFindKey(Handle:kv, const String:name[]);

In the second example, the user must explicitly call CloseHandle simply for finding a sub-section. Not only is this extremely annoying, but also inefficient, and would lead to a myriad of memory leaks due to people forgetting to close simple iterations everywhere.

C++ users don’t have to worry about this, of course. This is a consequence of pointers needing to be stuffed through Handles combined with the lack of a garbage collector.

Events in SourceMod

May 4th, 2007

Today we’re introducing another wiki article: Events (SourceMod Scripting).

Events are one of the nicest additions to HL2, providing a message passing system similar to network messages, but more user friendly and much easier to debug. You’ll notice one major piece missing from the API: enumeration. Event properties are not enumerable.

Unfortunately, this was only a feature in Valve’s original API, IGameEventManager. When Valve added IGameEventManager2 as a more optimized API, they never bothered to expose the KeyValues functionality implicit in IGameEvent (implemented as CGameEvent). Since the original API is deprecated, SourceMod uses the newer version.

Of course, SourceMod’s event API is completely unrelated to AMX Mod X’s register_event, which was based on user messages. The Half-Life 2 game event manager only touches netcode when it transmits events (for which there is a special svc code). (AMX Mod X’s function should have been named register_message from the start.)

ConVars (cvars) in SourceMod

May 2nd, 2007

Today’s article is mostly a wiki addition: ConVars (SourceMod Scripting).

The major difference between SourceMod’s cvar API and AMX Mod X’s is how cvars are addressed. AMX Mod X references cvars by name, a string. SourceMod’s cvar API references by a Handle, which contains the ConVar pointer.

AMX Mod X’s failure to use the HL1 cvar_t pointer is nothing short of horrendous. The only benefit is that it makes coding much easier for newcomers. Unfortunately, it’s a huge performance issue. There are hundreds of cvars, and doing named lookups for every cvar operation is extremely painful, especially if the same cvar is used over and over again.

Even more concerning, it took the AMX Mod X developers (me included) a long time to notice this. Only later in the 1.x branch were “pcvar” natives introduced. These natives dealt with direct cvar_t pointers, and used the engine call SET_CVAR_DIRECT to make changes. One must wonder why HL1 even had SET_CVAR_FLOAT and SET_CVAR_STRING, as it basically encourages using much slower code.

While many people still use AMX Mod X’s name based lookup, “pcvar” usage is encouraged. Of course with SourceMod, it is the only option.

SourceMod maintains similarity with AMX Mod X’s “pcvar” API by using one Handle for every named cvar. If two plugins create or find the same cvar, they will both receive the same Handle. Thus, no plugin owns any cvars, and cvars are not removed until SourceMod unloads. This has a few benefits:

  • You can directly compare cvar Handles. For example, instead of doing a strcmp() call, you can do if (hndl1 == hndl2)
  • If a plugin unloads, the cvar will stick around, so any settings will still be set if the plugin reloads or refreshes.
  • Plugins don’t have to call CloseHandle() simply for finding a ConVar

The other major difference is that HL2 engine passes ConVar changes through a callback. Thus, you can detect changes without making a secondary console command. This was not possible in HL1. Although you could hook SET_CVAR_FLOAT, it was not virtual, and thus internal or plugin usage of it would not be caught. Also, the change callbacks are per-ConVar; HL1′s is global, and thus a bit less optimized.

On Timer Design, Part 3 of 3

April 30th, 2007

Today let’s finish up two previous articles on timer design.

As we learned, SourceMod drops features of previous timer designs in order to make it as streamlined as possible. Timers have one unchangeable interval. If you want them to repeat, they repeat until you explicitly tell them to stop. You can slide an optional integer through the callback, but no arrays with strange copyback expectations or parameter re-ordering messes. This design doesn’t restrict you. It simply means that you have to use the other primitives of SourceMod as building blocks for your scripts.

To read on, see today’s wiki article, Timers (SourceMod Scripting), where various common examples are addressed:

  • One-time timers
  • Killing timers without using a crutch like AMX Mod X’s task_exists()
  • Counting repeatable timers
  • Passing basic data to a timer
  • Passing advanced data to a timer

Writing a JIT, Part 6 – Helpers

April 27th, 2007

I have just completed a move from Rhode Island to Massachusetts. The devlog should be back in action again, with M-W-F updates.

Today we’ve released two tools we used to write the JIT.

jit_helpers.h
This a simple class which lets us do two pass JIT writing. It stores an input stream and an output stream. The output stream can be NULL, which is used for the first pass to calculate buffer sizes. The second pass sets the output stream to a valid memory block, in which case writes physically occur.

The input and output streams are mutually exclusive; the output stream is used for code generation. The input stream is used for reading data from the source p-code. They should never collide.

x86_macros.h
This is a long file which provides jit_helpers macros for writing x86 opcodes. Be warned – the macros are not very consistent, and if you don’t know x86 encoding rules, you won’t catch idiosyncracies like needing to use IA32_Sub_RmEBP_Imm8_Disp_Reg instead of IA32_Sub_Rm_Imm8_Disp32.

However, this provides us with a very simple way of generating opcodes while keeping the multipass system transparent, and abstracting the actual encoding of x86 features, such as Mod R/M and SIB.

Let’s break down a simple macro to see how they’re built:

#define IA32_NOT_RM				0xF7	// encoding is /2
 
inline jit_uint8_t ia32_modrm(jit_uint8_t mode, jit_uint8_t reg, jit_uint8_t rm)
{
	jit_uint8_t modrm = (mode << 6);
	modrm |= (reg << 3);
	modrm |= (rm);
	return modrm;
}
 
inline void IA32_Not_Rm(JitWriter *jit, jit_uint8_t reg, jit_uint8_t mode)
{
	jit->write_ubyte(IA32_NOT_RM);
	jit->write_ubyte(ia32_modrm(mode, 2, reg));
}

First we find the encoding using the NASM manual, which is usually correct, but sometimes needs to be verified. /2 means one argument encoded as Mod R/M, which the “R” bits being 2. We write the opcode as a ubyte, then use the ia32_modrm macro to encode the R/M byte. Notice that this macro does not accept a displacement — if we came across a situation where we needed displacement, we’d add an extra parameter. Using displacement modes with this macro, as it is, would result in malformed code generation.

So, how does this end up looking in our JIT? Here’s a quick example:

JitWriter jw;
JitWriter *jit = &jw;
jw.outbase = NULL;
 
multipass:
   jw.outptr = jw.outbase;
   IA32_Not_Rm(jit, REG_EAX, MOD_REG);
 
   if (jw.outbase == NULL)
   {
      /* outptr will now have the size */
      jw.outbase = VirtualAlloc(NULL, (size_t)jw.outptr, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
      goto multipass;
   }

This snippet of code compiles one opcode using exactly the amount of memory required.

Note: It appears the license on these two files is still internal; it will be switched to the GPL shortly.

Function Calling API Wiki Article

April 18th, 2007

Today’s article is a wiki article on Function Calling API (SourceMod Scripting).

This has been covered here before; this article briefly explains the difference between forwards and single calls, and the difference between global and private forwards. Most importantly, it demonstrates the plugin-level API for managing both.

Creating Natives (Scripting)

April 16th, 2007

Today’s addition is another update to the SDK and wiki: Creating Natives (SourceMod Scripting).

This article goes in depth about creating natives (“dynamic” or “fake” natives) from plugins, for other plugins to use. This was discussed previously here.

Scripting FAQ?

April 13th, 2007

After observing people attempt to write scripts in SourceMod over the past week or two, the same issues/questions have come up a few times. This is a quick summary of them.

Q: Do I need to unhook/free everything in OnPluginEnd?
A: No. Handles are automatically closed, and any data used by your plugin is cleaned up.

Q: Do I need to include every .inc I use?
A: No. If the API is stable, it will be automatically included from sourcemod.inc. You can check sourcemod.inc to verify this.

Q: Can I get cvar values in OnPluginStart?
A: If your plugin is loaded after map start, yes. If your plugin is loaded automatically, it will be before server.cfg is executed. Thus, cvars won’t be set to their final values yet. If necessary, you should define convar change hooks, or start a timer via CreateTimer.

Q: Is ClientDisconnect called on mapchange?
A: Yes.

Q: What’s the difference between a user id, a client index, an edict/entity index, and a CBaseEntity?
A: A CBaseEntity is networked via an entity – and every entity has an index. For the purposes of SourceMod, a CBaseEntity, entity, edict, and edict index are all the same thing. Additionally, a client index is also an entity index in SourceMod. In some places in the Source engine, a client index is the entity index minus one — but that technicality is discarded for simplicity. Lastly, a user id is a unique, 2 byte integer assigned to every user who enters the server. It increases by one for each player that joins.

Q: Should you save the result of FindSendPropOffs/FindDataMapOffs?
A: You don’t need to — it’s a very tiny optimization. The SetProp/GetProp functions use a very fast caching mechanism.