Archive for the ‘General’ Category

DevLog Discontinued!

Sunday, August 12th, 2007

From now on I will be making “programming article” style posts here: http://www.bailopan.net/blog. It is unlikely this area will be used further, since for SourceMod I tend to write normal programming documentation now.

I will be moving some of the better series/articles as I get the time.

Thanks for reading, all zero readers!

Benchmarks Against EventScripts

Thursday, June 7th, 2007

A long time ago we wrote benchmark charts comparing AMX Mod and AMX Mod X. It created a flurry of flaming, but the end result was — AMX Mod X performed better, but the comparisons were realistic.

Today we’re releasing: benchmarks against EventScripts. Needless to say, these comparisons have an astronomical difference, totally unlike the AMX Mod charts. Although we’re expecting a lot of flaming — Enjoy!

Benchmarks Against AMX Mod X

Wednesday, June 6th, 2007

After all this talk about optimizing SourceMod, has it paid off? Today I’ve updated our old AMX Mod X benchmark tests for SourceMod and added a few new tests.

The old test suite is available here.
Updated files for SourceMod support are here.

First, let’s show a pretty graph of the results (click to enlarge):

To keep things short, here’s a quick rundown of what’s faster and why:

  • Integer – We’ll chalk this one up to the opcode improvements of the JIT. Especially integer division got a big improvement, although this test covers the main four functions and modulus.
  • Float – The opcodes generated for float rounding got entirely redone by faluco and ended up much faster.
  • Format() – These are almost identical because SourceMod and AMX Mod X both have the same underlying code for formatting (“atcprintf”).
  • String Fetch – This is designed to test MF_GetAmxString/get_amxstring versus SourceMod’s IPluginContext::LocalToString by using str_to_num and StringToInt. SourceMod’s is obviously faster because there is no cell * to char * copying taking place, as there is with AMX Mod X.
  • Translation – SourceMod has a more efficient translation lookup cache, which uses a trie instead of a hash table.
  • Replace() – SourceMod’s ReplaceStringEx() is probably actually slower than AMX Mod X’s replace(). However, AMX Mod X doesn’t have a native implementation for replacing all occurrences, and SourceMod does. SourceMod’s single-replace function accounts for more cases internally so it can do replacements on buffers which are not long enough.

So, SourceMod is indeed marginally faster or more than AMX Mod X for most of the fundamental operations, which are integer and float math, and string fetching/translation.

Scripting FAQ?

Friday, 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.

Small Hiatus

Wednesday, March 28th, 2007

The SourceMod Dev Log will be on a short haitus until Monday.

Whoa?!

Friday, March 16th, 2007

Although a bit late in the day, the real news is over here.

Handle System API, Part 3 of 3

Monday, February 26th, 2007

Last article, we saw that Handles could be blocked from CloseHandle on a per-type basis. However, there are situations when you only want some Handles of a type to be blocked. This is addressed in today’s newest addition to the Handle System API article.

You may note that the article has been altered; there are now per-Handle permissions. Originally, there was no way to do this. In fact, this feature was added only a day ago. However, there are pieces of SourceMod already implemented which required the feature, so how was it done in the past?

We used the fact that child types do not inherit permissions from their parent. Imagine two types using BitBuffers as an example: a “read only BitBuffer” and a “writable BitBuffer.” First, you create a “writable BitBuffer” type. Then, you create a second, read-only type with the writable type as its parent. Because of the parent/child relationship, the same set of functions would work on both types; but CloseHandle() would only work on the writable version. This works because you can specify new security rules for child types.

Why did we opt for such a complex solution at first, rather than just implementing per-Handle permissions from the start? There’s no real excuse, but this was the mindset:

  • We did not actually foresee this being useful enough at the time. Each Handle uses about 40 bytes in memory, and the block of pre-allocated Handles is 16K entries long. That’s roughly a 660KB block of consecutive memory, and per-Handle permissions would increase that to 60 bytes per Handle (~990KB), so we opted to not do it.
  • The permissions system was already completed and in use by the time we realized the design flaw, and thus new functions would have to be added for backwards compatibility.

One thing to note about the prior workaround is that the order of inheritance does not matter. The parent OR the child type can be the locked type. The Handle System doesn’t care, as the types internally do not actually share anything. However, you must remember to use the parent type for generic accesses. If you use the child type, and a Handle of the parent type is passed in, the type check routine will fail. This is the same as in C++: You can’t necessarily cast a base type to a derived type safely.

Internally, the implementation of the new per-Handle security is optimized for backwards compatibility. The “quick” way to implement this, in pseudo code, would have been:

handle->access = access ? access : handle->type->access

This would make the Handle permissions equal to either user-supplied, or the type’s default, access rules. But the implementation is slightly different. CreateHandle() is now a wrapper around CreateHandleEx() (which itself wraps an internal function called CreateHandleInt(), which wraps MakePrimHandle!), and it passes NULL as the access structure — meaning that CreateHandle is general usage and will be called more often. So to prevent copying the entire permissions struct on every “normal” Handle create, it looks a bit different.

IF access
   handle->special_rules = true
   handle->access = access
ELSE
   handle->special_rules = false

Internally, there is a simple CheckAccess() function which can decode this:

CheckAccess(handle, right, identity)
{
   access = handle->special_rules ? handle->access : handle->type->access
   if (access[right] & HANDLE_RESTRICT_IDENTITY
       && handle->identity != identity)
   {
       return false
   }
   return true
}

(Of course, this isn’t really an “optimization,” but it’s in case the HandleAccess struct grows more complex over time.)

That about sums it up for Handle Security. You, as the rest of the SourceMod developers are, should be thoroughly sick of it by now.

Handle System API, Part 2 of 3

Friday, February 23rd, 2007

The newest addition to the Handle API article shows an example of modifying the security permissions of a type.

This is in fact more useful than it might seem at first, despite the bad example. There are a few areas in SourceMod where CloseHandle is not supported from plugins on certain Handle types:

  • ConVars: Can’t be closed or cloned. They are global and not owned by any plugin, and thus already totally shareable, so there’s no need to allow for deleting or cloning. They are only freed when SourceMod is unloaded.
  • SMC Parsers: Can’t be cloned. They contain function pointers to a specific plugin. If that plugin unloads, the Handle’s internal contents are useless. Thus, they cannot be cloned into another plugin.
  • Plugins: These uniquely identify a plugin. If the plugin is unloaded, the Handle is destroyed, so naturally they cannot be cloned. They cannot be deleted either; it would be a disaster if a plugin accidentally called CloseHandle() on itself!
  • BitBuffers/Events: Closeable if manually created, but not if passed through a forward.

All of these, except the last, are implemented in a manner similar to today’s example. What’s special about the last? BitBuffers and Events require per-Handle permissions, which do not yet exist. Getting around this will be explained in Monday’s article.

Note: Unfortunately, this article was originally much longer, but it somehow got corrupted. Monday’s Wiki entry should be more detailed.