/** * @brief Reset and free the UI data hunk * @note Even called in case of an error when CL_Shutdown was called - maybe even * before CL_InitLocal (and thus UI_Init) was called * @sa CL_Shutdown * @sa UI_Init */ void UI_Shutdown (void) { /* MN is not yet initialized */ if (ui_global.adata == nullptr) return; const uiBehaviour_t* confunc = UI_GetNodeBehaviour("confunc"); /* remove all confunc commands */ for (int i = 0; i < ui_global.numWindows; i++) { uiNode_t* node = ui_global.windows[i]; while (node) { /* remove the command */ if (node->behaviour == confunc) { /* many nodes to one command is allowed */ if (Cmd_Exists(node->name)) Cmd_RemoveCommand(node->name); } /* recursive next */ if (node->firstChild != nullptr) { node = node->firstChild; continue; } while (node) { if (node->next != nullptr) { node = node->next; break; } node = node->parent; } } } UI_FontShutdown(); UI_ResetInput(); UI_ResetTimers(); #ifdef DEBUG Cmd_RemoveCommand("debug_uimemory"); #endif Cmd_RemoveCommand("ui_restart"); Mem_Free(ui_global.adata); OBJZERO(ui_global); /* release pools */ Mem_FreePool(ui_sysPool); Mem_FreePool(ui_dynStringPool); Mem_FreePool(ui_dynPool); ui_sysPool = nullptr; ui_dynStringPool = nullptr; ui_dynPool = nullptr; }
/** * @brief test behaviour name */ static void testKnownBehaviours (void) { uiBehaviour_t* behaviour; behaviour = UI_GetNodeBehaviour("button"); CU_ASSERT_FATAL(behaviour != nullptr); CU_ASSERT_STRING_EQUAL(behaviour->name, "button"); /* first one */ behaviour = UI_GetNodeBehaviour(""); CU_ASSERT(behaviour != nullptr); /* last one */ behaviour = UI_GetNodeBehaviour("zone"); CU_ASSERT_FATAL(behaviour != nullptr); CU_ASSERT_STRING_EQUAL(behaviour->name, "zone"); /* unknown */ behaviour = UI_GetNodeBehaviour("dahu"); CU_ASSERT(behaviour == nullptr); }
/** * @brief Reset and free the UI data hunk * @note Even called in case of an error when CL_Shutdown was called - maybe even * before CL_InitLocal (and thus UI_Init) was called * @sa CL_Shutdown * @sa UI_Init */ void UI_Shutdown (void) { int i; const uiBehaviour_t *confunc; /* MN is not yet initialized */ if (ui_global.adata == NULL) return; confunc = UI_GetNodeBehaviour("confunc"); /* remove all confunc commands */ for (i = 0; i < ui_global.numWindows; i++) { uiNode_t *node = ui_global.windows[i]; while (node) { /* remove the command */ if (node->behaviour == confunc) { /* many nodes to one command is allowed */ if (Cmd_Exists(node->name)) Cmd_RemoveCommand(node->name); } /* recursive next */ if (node->firstChild != NULL) node = node->firstChild; else { while (node) { if (node->next != NULL) { node = node->next; break; } node = node->parent; } } } } if (ui_global.adataize) Mem_Free(ui_global.adata); ui_global.adata = NULL; ui_global.adataize = 0; /* release pools */ Mem_FreePool(ui_sysPool); Mem_FreePool(ui_dynStringPool); Mem_FreePool(ui_dynPool); ui_sysPool = NULL; ui_dynStringPool = NULL; ui_dynPool = NULL; }
/** * @brief Allocate a node into the UI memory (do notr call behaviour->new) * @note It's not a dynamic memory allocation. Please only use it at the loading time * @todo Assert out when we are not in parsing/loading stage * @param[in] name Name of the new node, else NULL if we dont want to edit it. * @param[in] type Name of the node behavior * @param[in] isDynamic Allocate a node in static or dynamic memory */ static uiNode_t* UI_AllocNodeWithoutNew (const char* name, const char* type, qboolean isDynamic) { uiNode_t* node; uiBehaviour_t *behaviour; int nodeSize; behaviour = UI_GetNodeBehaviour(type); if (behaviour == NULL) Com_Error(ERR_FATAL, "UI_AllocNodeWithoutNew: Node behaviour '%s' doesn't exist", type); nodeSize = sizeof(*node) + behaviour->extraDataSize; if (!isDynamic) { if (ui_global.curadata + nodeSize > ui_global.adata + ui_global.adataize) Com_Error(ERR_FATAL, "UI_AllocNodeWithoutNew: No more memory to allocate a new node"); /** @todo fix this hard coded '8' value */ ui_global.curadata = ALIGN_PTR(ui_global.curadata, 8); node = (uiNode_t*) ui_global.curadata; ui_global.curadata += nodeSize; ui_global.numNodes++; memset(node, 0, nodeSize); } else { node = (uiNode_t*)Mem_PoolAlloc(nodeSize, ui_dynPool, 0); memset(node, 0, nodeSize); node->dynamic = qtrue; } node->behaviour = behaviour; #ifdef DEBUG node->behaviour->count++; #endif if (node->behaviour->isAbstract) Com_Error(ERR_FATAL, "UI_AllocNodeWithoutNew: Node behavior '%s' is abstract. We can't instantiate it.", type); if (name != NULL) { Q_strncpyz(node->name, name, sizeof(node->name)); if (strlen(node->name) != strlen(name)) Com_Printf("UI_AllocNodeWithoutNew: Node name \"%s\" truncated. New name is \"%s\"\n", name, node->name); } /* initialize default properties */ if (node->behaviour->loading) node->behaviour->loading(node); return node; }
/** * @brief Allocate a node into the UI memory (do not call behaviour->new) * @note It's not a dynamic memory allocation. Please only use it at the loading time * @todo Assert out when we are not in parsing/loading stage * @param[in] name Name of the new node, else nullptr if we don't want to edit it. * @param[in] type Name of the node behavior * @param[in] isDynamic Allocate a node in static or dynamic memory */ static uiNode_t* UI_AllocNodeWithoutNew (const char* name, const char* type, bool isDynamic) { uiNode_t* node; uiBehaviour_t *behaviour; int nodeSize; behaviour = UI_GetNodeBehaviour(type); if (behaviour == nullptr) Com_Error(ERR_FATAL, "UI_AllocNodeWithoutNew: Node behaviour '%s' doesn't exist", type); nodeSize = sizeof(*node) + behaviour->extraDataSize; if (!isDynamic) { void *memory = UI_AllocHunkMemory(nodeSize, STRUCT_MEMORY_ALIGN, true); if (memory == nullptr) Com_Error(ERR_FATAL, "UI_AllocNodeWithoutNew: No more memory to allocate a new node - increase the cvar ui_hunksize"); node = static_cast<uiNode_t*>(memory); ui_global.numNodes++; } else { node = static_cast<uiNode_t*>(Mem_PoolAlloc(nodeSize, ui_dynPool, 0)); node->dynamic = true; } node->behaviour = behaviour; #ifdef DEBUG UI_Node_DebugCountWidget(node, 1); #endif if (UI_Node_IsAbstract(node)) Com_Error(ERR_FATAL, "UI_AllocNodeWithoutNew: Node behavior '%s' is abstract. We can't instantiate it.", type); if (name != nullptr) { Q_strncpyz(node->name, name, sizeof(node->name)); if (strlen(node->name) != strlen(name)) Com_Printf("UI_AllocNodeWithoutNew: Node name \"%s\" truncated. New name is \"%s\"\n", name, node->name); } /* initialize default properties */ UI_Node_Loading(node); return node; }
/** * @brief after execution of a unittest window, it analyse color of * normalized indicator, and create asserts. Both Fail/Pass asserts is * useful to count number. */ static void UFO_AnalyseTestWindow (const char* windowName) { const uiBehaviour_t* stringBehaviour = UI_GetNodeBehaviour("string"); const uiNode_t* node; const uiNode_t* window = UI_GetWindow(windowName); CU_ASSERT_FATAL(window != nullptr); CU_ASSERT_FATAL(stringBehaviour != nullptr); for (node = window->firstChild; node != nullptr; node = node->next) { bool isGreen; bool isRed; /* skip non "string" nodes */ if (node->behaviour != stringBehaviour) continue; if (node->invis) continue; /* skip nodes without "test" prefix */ if (!Q_strstart(node->name, "test")) continue; isGreen = node->color[0] < 0.1 && node->color[1] > 0.9 && node->color[2] < 0.1; isRed = node->color[0] > 0.9 && node->color[1] < 0.1 && node->color[2] < 0.1; if (isGreen) { /** @note Useful to count number of tests */ CU_ASSERT_TRUE(true); } else if (isRed) { const char* message = va("%s.%s failed.", windowName, node->name); /*Com_Printf("Error: %s\n", message);*/ /*CU_FAIL(message);*/ CU_assertImplementation(CU_FALSE, 0, va("CU_FAIL(%s)", message), va("base/ufos/uitest/%s.ufo", windowName), "", CU_FALSE); } else { const char* message = va("%s.%s have an unknown status.", windowName, node->name); /*Com_Printf("Warning: %s\n", message);*/ /*CU_FAIL(message);*/ CU_assertImplementation(CU_FALSE, 0, va("CU_FAIL(%s)", message), va("base/ufos/uitest/%s.ufo", windowName), "", CU_FALSE); } } }
/** * @brief Parse a component * @sa CL_ParseClientData * @code * component panel componentName { * } * @endcode */ bool UI_ParseComponent (const char* type, const char* name, const char** text) { const char* errhead = "UI_ParseComponent: unexpected end of file (component"; const char* token; if (!Q_streq(type, "component")) { Com_Error(ERR_FATAL, "UI_ParseComponent: \"component\" expected but \"%s\" found.\n", type); return false; /* never reached */ } /* check the name */ if (!UI_TokenIsName(name, false)) { Com_Printf("UI_ParseNode: \"%s\" is not a well formed node name ([a-zA-Z_][a-zA-Z0-9_]*)\n", name); return false; } if (UI_TokenIsReserved(name)) { Com_Printf("UI_ParseNode: \"%s\" is a reserved token, we can't call a node with it\n", name); return false; } token = Com_EParse(text, errhead, ""); if (text == nullptr) return false; /* get keyword */ if (!Q_streq(token, "extends")) { Com_Printf("UI_ParseComponent: \"extends\" expected but \"%s\" found (component %s)\n", token, name); return false; } token = Com_EParse(text, errhead, ""); if (text == nullptr) return false; /* initialize component */ uiNode_t* component = nullptr; const uiBehaviour_t* behaviour = UI_GetNodeBehaviour(token); if (behaviour) { /* initialize a new node from behaviour */ component = UI_AllocNode(name, behaviour->name, false); } else { const uiNode_t* inheritedComponent = UI_GetComponent(token); if (inheritedComponent) { /* initialize from a component */ component = UI_CloneNode(inheritedComponent, nullptr, true, name, false); } else { Com_Printf("UI_ParseComponent: node behaviour/component '%s' doesn't exists (component %s)\n", token, name); return false; } } /* get body */ token = Com_EParse(text, errhead, ""); if (!*text) return false; bool result = UI_ParseNodeBody(component, text, &token, errhead); if (!result) return false; /* validate properties */ UI_Node_Loaded(component); UI_InsertComponent(component); return true; }
/** * @brief parse a node * @sa UI_ParseNodeProperties * @todo we can think about merging UI_ParseNodeProperties here * @note first token already read * @note dont read more than the need token (last right token is '}' of end of node) */ static uiNode_t* UI_ParseNode (uiNode_t* parent, const char** text, const char** token, const char* errhead) { uiNode_t* node = nullptr; uiBehaviour_t* behaviour; uiNode_t* component = nullptr; /* get the behaviour */ behaviour = UI_GetNodeBehaviour(*token); if (!behaviour) { component = UI_GetComponent(*token); } if (behaviour == nullptr && component == nullptr) { Com_Printf("UI_ParseNode: node behaviour/component '%s' doesn't exist (%s)\n", *token, UI_GetPath(parent)); return nullptr; } /* get the name */ *token = Com_EParse(text, errhead, ""); if (!*text) return nullptr; if (!UI_TokenIsName(*token, Com_GetType(text) == TT_QUOTED_WORD)) { Com_Printf("UI_ParseNode: \"%s\" is not a well formed node name ([a-zA-Z_][a-zA-Z0-9_]*)\n", *token); return nullptr; } if (UI_TokenIsReserved(*token)) { Com_Printf("UI_ParseNode: \"%s\" is a reserved token, we can't call a node with it\n", *token); return nullptr; } /* test if node already exists */ /* Already existing node should only come from inherited node, we should not have 2 definitions of the same node into the same window. */ if (parent) node = UI_GetNode(parent, *token); /* reuse a node */ if (node) { const uiBehaviour_t* test = (behaviour != nullptr) ? behaviour : (component != nullptr) ? component->behaviour : nullptr; if (node->behaviour != test) { Com_Printf("UI_ParseNode: we can't change node type (node \"%s\")\n", UI_GetPath(node)); return nullptr; } Com_DPrintf(DEBUG_CLIENT, "... over-riding node %s\n", UI_GetPath(node)); /* else initialize a component */ } else if (component) { node = UI_CloneNode(component, nullptr, true, *token, false); if (parent) { if (parent->root) UI_UpdateRoot(node, parent->root); UI_AppendNode(parent, node); } /* else initialize a new node */ } else { node = UI_AllocNode(*token, behaviour->name, false); node->parent = parent; if (parent) node->root = parent->root; /** @todo move it into caller */ if (parent) UI_AppendNode(parent, node); } /* get body */ const bool result = UI_ParseNodeBody(node, text, token, errhead); if (!result) return nullptr; /* validate properties */ UI_Node_Loaded(node); return node; }
/** * Initializes the inheritance (every node extends the abstract node) * @param behaviour The behaviour to initialize */ static void UI_InitializeNodeBehaviour (uiBehaviour_t* behaviour) { if (behaviour->isInitialized) return; /** @todo check (when its possible) properties are ordered by name */ /* check and update properties data */ if (behaviour->properties) { int num = 0; const value_t* current = behaviour->properties; while (current->string != NULL) { num++; current++; } behaviour->propertyCount = num; } /* everything inherits 'abstractnode' */ if (behaviour->extends == NULL && !Q_streq(behaviour->name, "abstractnode")) { behaviour->extends = "abstractnode"; } if (behaviour->extends) { int i = 0; behaviour->super = UI_GetNodeBehaviour(behaviour->extends); UI_InitializeNodeBehaviour(behaviour->super); while (qtrue) { const size_t pos = virtualFunctions[i]; uintptr_t superFunc; uintptr_t func; if (pos == -1) break; /* cache super function if we don't overwrite it */ superFunc = *(uintptr_t*)((byte*)behaviour->super + pos); func = *(uintptr_t*)((byte*)behaviour + pos); if (func == 0 && superFunc != 0) *(uintptr_t*)((byte*)behaviour + pos) = superFunc; i++; } } /* property must not overwrite another property */ if (behaviour->super && behaviour->properties) { const value_t* property = behaviour->properties; while (property->string != NULL) { const value_t *p = UI_GetPropertyFromBehaviour(behaviour->super, property->string); #if 0 /**< @todo not possible at the moment, not sure its the right way */ const uiBehaviour_t *b = UI_GetNodeBehaviour(current->string); #endif if (p != NULL) Com_Error(ERR_FATAL, "UI_InitializeNodeBehaviour: property '%s' from node behaviour '%s' overwrite another property", property->string, behaviour->name); #if 0 /**< @todo not possible at the moment, not sure its the right way */ if (b != NULL) Com_Error(ERR_FATAL, "UI_InitializeNodeBehaviour: property '%s' from node behaviour '%s' use the name of an existing node behaviour", property->string, behaviour->name); #endif property++; } } /* Sanity: A property must not be outside the node memory */ if (behaviour->properties) { const int size = sizeof(uiNode_t) + behaviour->extraDataSize; const value_t* property = behaviour->properties; while (property->string != NULL) { if (property->type != V_UI_NODEMETHOD && property->ofs + property->size > size) Com_Error(ERR_FATAL, "UI_InitializeNodeBehaviour: property '%s' from node behaviour '%s' is outside the node memory. The C code need a fix.", property->string, behaviour->name); property++; } } behaviour->isInitialized = qtrue; }
/** * @brief Initialize a node behaviour memory, after registration, and before unsing it. * @param behaviour Behaviour to initialize */ void UI_InitializeNodeBehaviour (uiBehaviour_t* behaviour) { if (behaviour->isInitialized) return; /* everything inherits 'abstractnode' */ if (behaviour->extends == NULL && !Q_streq(behaviour->name, "abstractnode")) { behaviour->extends = "abstractnode"; } if (behaviour->extends) { int i = 0; /** TODO Find a way to remove that, if possible */ behaviour->super = UI_GetNodeBehaviour(behaviour->extends); UI_InitializeNodeBehaviour(behaviour->super); while (qtrue) { const size_t pos = virtualFunctions[i]; uintptr_t superFunc; uintptr_t func; if (pos == -1) break; /* cache super function if we don't overwrite it */ superFunc = *(uintptr_t*)((byte*)behaviour->super + pos); func = *(uintptr_t*)((byte*)behaviour + pos); if (func == 0 && superFunc != 0) *(uintptr_t*)((byte*)behaviour + pos) = superFunc; i++; } } /* sort properties by alphabet */ if (behaviour->localProperties) { int i = 0; const value_t* previous; const value_t** oldmemory = behaviour->localProperties; behaviour->localProperties = UI_AllocHunkMemory(sizeof(value_t*) * (behaviour->propertyCount+1), STRUCT_MEMORY_ALIGN, qfalse); if (behaviour->localProperties == NULL) { Com_Error(ERR_FATAL, "UI_InitializeNodeBehaviour: UI memory hunk exceeded - increase the size"); } previous = NULL; for (i = 0; i < behaviour->propertyCount; i++) { const value_t* better = NULL; const value_t** current; /* search the next element after previous */ for (current = oldmemory; *current != NULL; current++) { if (previous != NULL && Q_strcasecmp(previous->string, (*current)->string) >= 0) { continue; } if (better == NULL || Q_strcasecmp(better->string, (*current)->string) >= 0) { better = *current; } } previous = better; behaviour->localProperties[i] = better; } behaviour->localProperties[behaviour->propertyCount] = NULL; Mem_Free(oldmemory); } /* property must not overwrite another property */ if (behaviour->super && behaviour->localProperties) { const value_t** property = behaviour->localProperties; while (*property) { const value_t *p = UI_GetPropertyFromBehaviour(behaviour->super, (*property)->string); #if 0 /**< @todo not possible at the moment, not sure its the right way */ const uiBehaviour_t *b = UI_GetNodeBehaviour(current->string); #endif if (p != NULL) Com_Error(ERR_FATAL, "UI_InitializeNodeBehaviour: property '%s' from node behaviour '%s' overwrite another property", (*property)->string, behaviour->name); #if 0 /**< @todo not possible at the moment, not sure its the right way */ if (b != NULL) Com_Error(ERR_FATAL, "UI_InitializeNodeBehaviour: property '%s' from node behaviour '%s' use the name of an existing node behaviour", (*property)->string, behaviour->name); #endif property++; } } /* Sanity: A property must not be outside the node memory */ if (behaviour->localProperties) { const int size = sizeof(uiNode_t) + behaviour->extraDataSize; const value_t** property = behaviour->localProperties; while (*property) { if ((*property)->type != V_UI_NODEMETHOD && (*property)->ofs + (*property)->size > size) Com_Error(ERR_FATAL, "UI_InitializeNodeBehaviour: property '%s' from node behaviour '%s' is outside the node memory. The C code need a fix.", (*property)->string, behaviour->name); property++; } } behaviour->isInitialized = qtrue; }