diff --git a/amxmodx/AMBuilder b/amxmodx/AMBuilder index 9f5fc5be..16125cdc 100644 --- a/amxmodx/AMBuilder +++ b/amxmodx/AMBuilder @@ -92,7 +92,8 @@ binary.sources = [ 'datastructs.cpp', 'trie_natives.cpp', 'CDataPack.cpp', - 'datapacks.cpp' + 'datapacks.cpp', + 'stackstructs.cpp', ] if builder.target_platform == 'windows': diff --git a/amxmodx/Makefile b/amxmodx/Makefile index 42982f82..fc136574 100755 --- a/amxmodx/Makefile +++ b/amxmodx/Makefile @@ -21,7 +21,7 @@ OBJECTS = meta_api.cpp CFile.cpp CVault.cpp vault.cpp float.cpp file.cpp modules CMenu.cpp util.cpp amx.cpp amxdbg.cpp natives.cpp newmenus.cpp debugger.cpp \ optimizer.cpp format.cpp messages.cpp libraries.cpp vector.cpp sorting.cpp \ nongpl_matches.cpp CFlagManager.cpp datastructs.cpp \ - trie_natives.cpp CDataPack.cpp datapacks.cpp + trie_natives.cpp CDataPack.cpp datapacks.cpp stackstructs.cpp ############################################## ### CONFIGURE ANY OTHER FLAGS/OPTIONS HERE ### diff --git a/amxmodx/amxmodx.h b/amxmodx/amxmodx.h index bf55dc7d..1cc830a7 100755 --- a/amxmodx/amxmodx.h +++ b/amxmodx/amxmodx.h @@ -85,6 +85,7 @@ extern AMX_NATIVE_INFO msg_Natives[]; extern AMX_NATIVE_INFO vector_Natives[]; extern AMX_NATIVE_INFO g_SortNatives[]; extern AMX_NATIVE_INFO g_DataStructNatives[]; +extern AMX_NATIVE_INFO g_StackNatives[]; #if defined(_WIN32) #define DLLOAD(path) (DLHANDLE)LoadLibrary(path) diff --git a/amxmodx/datastructs.h b/amxmodx/datastructs.h index 15b94d53..491d4c1a 100644 --- a/amxmodx/datastructs.h +++ b/amxmodx/datastructs.h @@ -230,4 +230,4 @@ inline CellArray* HandleToVector(AMX* amx, int handle) } -#endif +#endif \ No newline at end of file diff --git a/amxmodx/modules.cpp b/amxmodx/modules.cpp index 627fafe8..282147d3 100755 --- a/amxmodx/modules.cpp +++ b/amxmodx/modules.cpp @@ -573,6 +573,7 @@ int set_amxnatives(AMX* amx, char error[128]) amx_Register(amx, g_DataStructNatives, -1); amx_Register(amx, trie_Natives, -1); amx_Register(amx, g_DatapackNatives, -1); + amx_Register(amx, g_StackNatives, -1); //we're not actually gonna check these here anymore amx->flags |= AMX_FLAG_PRENIT; diff --git a/amxmodx/msvc10/amxmodx_mm.vcxproj b/amxmodx/msvc10/amxmodx_mm.vcxproj index 113429f7..1c6a0e34 100644 --- a/amxmodx/msvc10/amxmodx_mm.vcxproj +++ b/amxmodx/msvc10/amxmodx_mm.vcxproj @@ -329,6 +329,7 @@ + All All @@ -400,6 +401,7 @@ + diff --git a/amxmodx/msvc10/amxmodx_mm.vcxproj.filters b/amxmodx/msvc10/amxmodx_mm.vcxproj.filters index f916cde8..4fff2988 100644 --- a/amxmodx/msvc10/amxmodx_mm.vcxproj.filters +++ b/amxmodx/msvc10/amxmodx_mm.vcxproj.filters @@ -174,6 +174,9 @@ Source Files + + Source Files + @@ -405,6 +408,9 @@ Pawn Includes + + Pawn Includes + diff --git a/amxmodx/stackstructs.cpp b/amxmodx/stackstructs.cpp new file mode 100644 index 00000000..586412c3 --- /dev/null +++ b/amxmodx/stackstructs.cpp @@ -0,0 +1,293 @@ +/* AMX Mod X + * + * by the AMX Mod X Development Team + * originally developed by OLO + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + */ +#include "amxmodx.h" +#include "datastructs.h" + +// native Stack:CreateStack(blocksize = 1); +static cell AMX_NATIVE_CALL CreateStack(AMX* amx, cell* params) +{ + int cellsize = params[1]; + + if (cellsize <= 0) + { + LogError(amx, AMX_ERR_NATIVE, "Invalid block size (must be > 0)", cellsize); + return -1; + } + + // Scan through the vector list to see if any are NULL. + // NULL means the vector was previously destroyed. + for (unsigned int i = 0; i < VectorHolder.length(); ++i) + { + if (VectorHolder[i] == NULL) + { + VectorHolder[i] = new CellArray(cellsize); + return i + 1; + } + } + + // None are NULL, create a new vector + CellArray* NewVector = new CellArray(cellsize); + + VectorHolder.append(NewVector); + + return VectorHolder.length(); +} + +// native PushStackCell(Stack:handle, any:value); +static cell AMX_NATIVE_CALL PushStackCell(AMX* amx, cell* params) +{ + CellArray* vec = HandleToVector(amx, params[1]); + + if (vec == NULL) + { + return 0; + } + + cell *blk = vec->push(); + + if (!blk) + { + LogError(amx, AMX_ERR_NATIVE, "Failed to grow stack"); + return 0; + } + + *blk = params[2]; + + return 1; +} + +// native PushStackString(Stack:handle, const value[]); +static cell AMX_NATIVE_CALL PushStackString(AMX* amx, cell* params) +{ + CellArray* vec = HandleToVector(amx, params[1]); + + if (vec == NULL) + { + return 0; + } + + cell *blk = vec->push(); + + if (!blk) + { + LogError(amx, AMX_ERR_NATIVE, "Failed to grow stack"); + return 0; + } + + int len; + const char *value = get_amxstring(amx, params[2], 0, len); + + strncopy(blk, value, vec->blocksize()); + + return 1; +} + +// native PushStackArray(Stack:handle, const any:values[], size= -1); +static cell AMX_NATIVE_CALL PushStackArray(AMX* amx, cell* params) +{ + CellArray* vec = HandleToVector(amx, params[1]); + + if (vec == NULL) + { + return 0; + } + + cell *blk = vec->push(); + + if (!blk) + { + LogError(amx, AMX_ERR_NATIVE, "Failed to grow stack"); + return 0; + } + + cell *addr = get_amxaddr(amx, params[2]); + size_t indexes = vec->blocksize(); + + if (params[3] != -1 && (size_t)params[3] <= vec->blocksize()) + { + indexes = params[3]; + } + + memcpy(blk, addr, indexes * sizeof(cell)); + + return 1; +} + +// native bool:PopStackCell(Stack:handle, &any:value, block = 0, bool:asChar = false); +static cell AMX_NATIVE_CALL PopStackCell(AMX* amx, cell* params) +{ + CellArray* vec = HandleToVector(amx, params[1]); + + if (vec == NULL) + { + return 0; + } + + if (vec->size() == 0) + { + return 0; + } + + cell *buffer = get_amxaddr(amx, params[2]); + size_t index = params[3]; + + cell *blk = vec->at(vec->size() - 1); + size_t idx = (size_t)params[3]; + + if (params[4] == 0) + { + if (idx >= vec->blocksize()) + { + LogError(amx, AMX_ERR_NATIVE, "Invalid block %d (blocksize: %d)", idx, vec->blocksize()); + return 0; + } + + *buffer = blk[idx]; + } + else + { + if (idx >= vec->blocksize() * 4) + { + LogError(amx, AMX_ERR_NATIVE, "Invalid byte %d (blocksize: %d bytes)", idx, vec->blocksize() * 4); + return 0; + } + + *buffer = (cell)*((char *)blk + idx); + } + + vec->remove(vec->size() - 1); + + return 1; +} + +// native bool:PopStackString(Stack:handle, buffer[], maxlength, &written = 0); +static cell AMX_NATIVE_CALL PopStackString(AMX* amx, cell* params) +{ + CellArray* vec = HandleToVector(amx, params[1]); + + if (vec == NULL) + { + return 0; + } + + if (vec->size() == 0) + { + return 0; + } + + size_t idx = vec->size() - 1; + cell *blk = vec->at(idx); + + int numWritten = set_amxstring_utf8(amx, params[2], blk, amxstring_len(blk), params[3] + 1); + *get_amxaddr(amx, params[4]) = numWritten; + + vec->remove(idx); + + return 1; +} + +// native bool:PopStackArray(Stack:handle, any:buffer[], size=-1); +static cell AMX_NATIVE_CALL PopStackArray(AMX* amx, cell* params) +{ + CellArray* vec = HandleToVector(amx, params[1]); + + if (vec == NULL) + { + return 0; + } + + if (vec->size() == 0) + { + return 0; + } + + size_t idx = vec->size() - 1; + cell *blk = vec->at(idx); + size_t indexes = vec->blocksize(); + + if (params[3] != -1 && (size_t)params[3] <= vec->blocksize()) + { + indexes = params[3]; + } + + cell *addr = get_amxaddr(amx, params[2]); + memcpy(addr, blk, indexes * sizeof(cell)); + + vec->remove(idx); + + return 1; +} + +// native bool:IsStackEmpty(Stack:handle); +static cell AMX_NATIVE_CALL IsStackEmpty(AMX* amx, cell* params) +{ + CellArray* vec = HandleToVector(amx, params[1]); + + if (vec == NULL) + { + return 0; + } + + return vec->size() == 0; +} + +// native DestroyStack(&Stack:which); +static cell AMX_NATIVE_CALL DestroyStack(AMX* amx, cell* params) +{ + cell *handle = get_amxaddr(amx, params[1]); + CellArray *vec = HandleToVector(amx, *handle); + + if (vec == NULL) + { + return 0; + } + + delete vec; + + VectorHolder[*handle - 1] = NULL; + + *handle = 0; + + return 1; +} + +AMX_NATIVE_INFO g_StackNatives[] = +{ + { "CreateStack", CreateStack }, + { "IsStackEmpty", IsStackEmpty }, + { "PopStackArray", PopStackArray }, + { "PopStackCell", PopStackCell }, + { "PopStackString", PopStackString }, + { "PushStackArray", PushStackArray }, + { "PushStackCell", PushStackCell }, + { "PushStackString", PushStackString }, + { "DestroyStack", DestroyStack }, + { NULL, NULL }, +}; diff --git a/plugins/include/amxmodx.inc b/plugins/include/amxmodx.inc index 674b0c99..6c11fddd 100755 --- a/plugins/include/amxmodx.inc +++ b/plugins/include/amxmodx.inc @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include diff --git a/plugins/include/cellstack.inc b/plugins/include/cellstack.inc new file mode 100644 index 00000000..ad5c083a --- /dev/null +++ b/plugins/include/cellstack.inc @@ -0,0 +1,146 @@ + +#if defined _cellstack_included + #endinput +#endif +#define _cellstack_included + +enum Stack +{ + Invalid_Stack = 0 +}; + +/** + * Creates a stack structure. A stack is a LIFO (last in, first out) + * vector (array) of items. It has O(1) insertion and O(1) removal. + * + * Stacks have two operations: Push (adding an item) and Pop (removes + * items in reverse-push order). + * + * The contents of the stack are uniform; i.e. storing a string and then + * retrieving it as an integer is NOT the same as StringToInt()! + * + * The "blocksize" determines how many cells each slot has; it cannot + * be changed after creation. + * + * @param blocksize The number of cells each entry in the stack can + * hold. For example, 32 cells is equivalent to: + * new Array[X][32] + * + * @return New stack Handle. + * @error Invalid block size. + */ +native Stack:CreateStack(blocksize = 1); + +/** + * Pushes a value onto the end of the stack, adding a new index. + * + * This may safely be used even if the stack has a blocksize + * greater than 1. + * + * @param handle Stack handle. + * @param value Value to push. + * + * @noreturn + * @error Invalid handle or out of memory. + */ +native PushStackCell(Stack:handle, any:value); + +/** + * Pushes a string onto the end of a stack, truncating it if it is + * too big. + * + * @param handle Stack handle. + * @param value String to push. + * + * @noreturn + * @error Invalid handle or out of memory. + */ +native PushStackString(Stack:handle, const value[]); + +/** + * Pushes an array of cells onto the end of a stack. The cells + * are pushed as a block (i.e. the entire array takes up one stack slot), + * rather than pushing each cell individually. + * + * @param handle Stack handle. + * @param values Block of values to copy. + * @param size If not set, the number of elements copied from the array + * will be equal to the blocksize. If set higher than the + * blocksize, the operation will be truncated. + * @noreturn + * @error Invalid handle or out of memory. + */ +native PushStackArray(Stack:handle, const any:values[], size= -1); + +/** + * Pops a cell value from a stack. + * + * @param handle Stack handle. + * @param value Variable to store the value. + * @param block Optionally specify which block to read from + * (useful if the blocksize > 0). + * @param asChar Optionally read as a byte instead of a cell. + * + * @return True on success, false if the stack is empty. + * @error Invalid handle, Invalid block or Invalid byte. + */ +native bool:PopStackCell(Stack:handle, &any:value, block = 0, bool:asChar = false); + +/** + * Pops a string value from a stack. + * + * @param handle Stack handle. + * @param buffer Buffer to store string. + * @param maxlength Maximum size of the buffer. + * @param written Number of characters copied. + * + * @return True on success, false if the stack is empty. + * @error Invalid handle. + */ +native bool:PopStackString(Stack:handle, buffer[], maxlength, &written = 0); + +/** + * Pops an array of cells from a stack. + * + * @param handle Stack handle. + * @param buffer Buffer to store the array in. + * @param size If not set, assumes the buffer size is equal to the + * blocksize. Otherwise, the size passed is used. + * + * @return True on success, false if the stack is empty. + * @error Invalid handle. + */ +native bool:PopStackArray(Stack:handle, any:buffer[], size = -1); + +/** + * Checks if a stack is empty. + * + * @param handle Stack handle. + * + * @return True if empty, false if not empty. + * @error Invalid handle. + */ +native bool:IsStackEmpty(Stack:handle); + +/** + * Pops a value off a stack, ignoring it completely. + * + * @param handle Stack handle. + * + * @return True if something was popped, false otherwise. + * @error Invalid handle. + */ +stock PopStack(Stack:handle) +{ + new value; + return PopStackCell(handle, value); +} + +/** + * Destroys the stack, and resets the handle to 0 to prevent accidental usage after it is destroyed. + * + * @param which The stack to destroy. + * + * @noreturn + */ +native DestroyStack(&Stack:handle); diff --git a/plugins/testsuite/stacktest.sma b/plugins/testsuite/stacktest.sma new file mode 100644 index 00000000..aa8be50a --- /dev/null +++ b/plugins/testsuite/stacktest.sma @@ -0,0 +1,73 @@ +#include + +new FailCount; +new PassCount; + +public plugin_init() +{ + register_plugin("Stack Tests", AMXX_VERSION_STR, "AMXX Dev Team"); + register_srvcmd("test_stack", "ServerCommand_TestStack"); +} + +assertEqual(const testname[], bool:pass) +{ + if (!pass) + { + server_print("[FAIL] %s", testname); + FailCount++; + } + else + { + server_print("[PASS] %s", testname); + PassCount++; + } +} + +done() +{ + server_print("Finished. %d tests, %d failed", FailCount + PassCount, FailCount); +} + +public ServerCommand_TestStack() +{ + new Stack:stack; + new test[20]; + new buffer[42]; + + test[0] = 5; + test[1] = 7; + + stack = CreateStack(30); + { + PushStackCell(stack, 50); + PushStackArray(stack, test, 2); + PushStackArray(stack, test, 2); + PushStackString(stack, "space craaab"); + PushStackCell(stack, 12); + } + + assertEqual("Size test #1", IsStackEmpty(stack) == false); + + PopStack(stack); + PopStackString(stack, buffer, charsmax(buffer)); + assertEqual("String test", bool:equal(buffer, "space craaab")); + + test[0] = 0; + test[1] = 0; + assertEqual("Array test #1", test[0] == 0 && test[1] == 0); + + PopStackArray(stack, test, 2); + assertEqual("Array test #1", test[0] == 5 && test[1] == 7); + + PopStackCell(stack, test[0], 1); + assertEqual("Value test #1", test[0] == 7); + + PopStackCell(stack, test[0]); + assertEqual("Value test #2", test[0] == 50); + + assertEqual("Size test #2", IsStackEmpty(stack) == true); + + DestroyStack(stack); + + done(); +} diff --git a/support/PackageScript b/support/PackageScript index f870ade3..fa1c605f 100644 --- a/support/PackageScript +++ b/support/PackageScript @@ -233,6 +233,7 @@ scripting_files = [ 'testsuite/sqlxtest.sql', 'testsuite/trietest.sma', 'testsuite/utf8test.sma', + 'testsuite/stacktest.sma', 'include/amxconst.inc', 'include/amxmisc.inc', 'include/amxmodx.inc',