Skip to content
IllidanS4 edited this page Jun 3, 2018 · 6 revisions

What is AMX

AMX is a virtual machine that executes AMX assembly code contained in .amx files that were compiled from Pawn. To understand some of the functions this plugin offers, understanding of the layout of the AMX machine is useful.

The memory in an AMX machine is segmented into several sections: the code, the data, the heap, and the stack. The code segment contains low-level instructions representing basic operations the machine can do (arithmetics, variable assignment, jumps, calls etc.). The data segment contains global and static variables that were defined in Pawn, i.e. those whose lifetime lasts for the duration of the whole program. The heap and the stack are temporary segments containing intermediate values of functions. The heap is usually used for storing arrays, while the stack is used for storing local variables and simple arguments to functions. The stack may be further segmented into stack frames created for each called function.

To run a code from an AMX machine, the host application must call it via one of its entry points. This can be a public function, the main function, or a continuation from a paused state. The code can exit from an entry point either naturally by returning from the public function, or by halting the AMX machine with an error code (by using #emit halt or amx_yield.

PawnPlus extensions

The plugin is notified when an AMX loads or unloads, and automatically manages the extra memory it needs for storing additional data bound to an AMX. This piece of metadata is called extra by the plugin and various parts of the plugin can dynamically register their own pieces of metadata. To prevent dangling pointers, shared_ptr is used to access these metadata inside the plugin.

Other pieces of extra information can be stored in a context. A context is a temporary state created by calling a public function, like when a stack frame is created for a normal function. Since instance of the AMX machine are usually permanent, storing information in a context has the advantage of automatic cleanup when resources used by the code bound by the context need to be automatically deleted. Functions like pawn_guard use the context as their storage, and when the context is deleted, the guards automatically free the resources as well.

Contexts are stored in a stack-like data structure for each AMX machine, since natives like CallLocalFunction can cause another public function in the same AMX to be called externally. When a public function is called normally (without a native), no new context is created.

Trans-AMX communication

When using multiple scripts, it may be hard to pass data from one script to another. Functions like CallRemoteFunction require parsing of parameters and finding the correct entry point in another AMX. This plugin allows to create a reference to a variable in the AMX machine's memory and pass that variable to a code in another AMX.

Such a reference can be created with amx_var pointing to a single cell, or amx_var_arr pointing to an array. The reference object holds both the handle to the AMX machine and the offset into its memory where the variable starts.

static var = 10;
new Var:ref = amx_var(var);
assert amx_valid(ref);
amx_set(ref, 15);
assert var == 15;
amx_delete(ref);

Every created reference must be deleted explicitly. amx_valid returns true if the reference can be accessed, which means it will return false even if the actual reference object stills exists but the target AMX is no longer available. These references are designed for low-level access to the memory of the AMX, thus they don't employ any sophisticated tag checks like variants and other dynamic data structures do.

Forking

AMX forking is an advanced way of creating a new context or an AMX machine from the current one. The primary function for this is amx_fork:

new result, Variant:var = var_new(0);
if(amx_fork(result))
{
    pawn_guard(var);
    amx_yield(15);
}
assert result == 15 && !var_valid(var);

By default, amx_fork creates a copy of the current AMX machine and runs the code that follows the function call. In the cloned AMX machine, the function returns true, and thus pawn_guard is called, guarding the variant. Once the code exits (by returning from the entry point or by calling amx_yield), the fork is destroyed and the code in the original AMX machine is executed from the original point, but with amx_fork returning false there. result is the variable which receives the value returned from the forked AMX machine. Since pawn_guard is bound to the current context, once the forked AMX exits, the context is destroyed and the variant is deleted.

The forked context doesn't have to be destroyed immediately. In the following case, the context is saved by calling wait_ms and restored after the given time:

new result;
if(amx_fork(result))
{
    task_yield(15);
    wait_ms(100);
    print("After 100 ms");
    amx_yield();
}
assert result == 15;

task_yield can be used as the usual mechanism to return a value from a code that is about to get paused.

To be continued.

Clone this wiki locally