/////////////////////////////////////////////////////////////////////////////////////// // // nodearray.cpp KSH 7 Nov 2001 // // Functions related to the NodeArray, such as the node name hash table, // prefix info for rapidly finding all the nodes with a given prefix, // and utility functions for getting the links and stuff. // /////////////////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include #include #include #include #include // For Crc::GenerateCRCFromString #include #include namespace SkateScript { using namespace Script; //////////////////////////////////////////////////////////////////// // // Node name hash table stuff, for speeding up FindNamedNode // //////////////////////////////////////////////////////////////////// #if 1//def __PLAT_NGC__ // Gets called from SkateScript::Init in sk\scripting\init.cpp void InitNodeNameHashTable() { } // Resets the hash table, and deletes any extra entries created. // This currently gets called from the rwviewer cleanup function. void ClearNodeNameHashTable() { } // Given that a new NodeArray symbol has got loaded due to the loading of a qb containing it, // this will generate the node name hash table. // More efficient hash table. #define HASH_SIZE 16384 #define HASH_MASK (HASH_SIZE-1) short node_hash[HASH_SIZE]; void CreateNodeNameHashTable() { for ( int lp = 0; lp < HASH_SIZE; lp++ ) { node_hash[lp] = -1; } // Get the NodeArray CArray *p_node_array=GetArray(0xc472ecc5/*NodeArray*/); Dbg_MsgAssert(p_node_array,("CreateNodeNameHashTable could not find NodeArray")); // Scan through each node, getting it's name, and if it has a name add it in to the hash table. for (uint32 i=0; iGetSize(); ++i) { CStruct *p_node=p_node_array->GetStructure(i); uint32 name_checksum=0; if (p_node->GetChecksum("Name",&name_checksum)) { uint16 hash = (uint16)(name_checksum & HASH_MASK ); while ( node_hash[hash] != -1 ) { hash++; hash &= HASH_MASK; } node_hash[hash] = i; } } } // Searches the node array for a node whose Name is the passed checksum. int FindNamedNode(uint32 checksum, bool assert) { // Get the NodeArray CArray *p_node_array=GetArray(CRCD(0xc472ecc5,"NodeArray")); Dbg_MsgAssert(p_node_array,("CreateNodeNameHashTable could not find NodeArray")); // Added 9/18/03 for THUG submission. If the node array doesn't exist, interpret that // as just another condition of the node not existing if( p_node_array == NULL ) { return -1; } uint16 hash = (uint16)(checksum & HASH_MASK ); while ( node_hash[hash] != -1 ) { CStruct *p_node=p_node_array->GetStructure(node_hash[hash]); if ( assert ) Dbg_MsgAssert(p_node,("NULL p_node")); uint32 name_checksum=0; if (p_node->GetChecksum(CRCD(0xa1dc81f9,"Name"),&name_checksum)) { if ( name_checksum == checksum ) { return node_hash[hash]; } } hash++; hash &= HASH_MASK; } // // Scan through each node, getting it's name, and if it has a name add it in to the hash table. // for (uint32 i=0; iGetSize(); ++i) // { // CStruct *p_node=p_node_array->GetStructure(i); // if ( assert ) Dbg_MsgAssert(p_node,("NULL p_node")); // // uint32 name_checksum=0; // if (p_node->GetChecksum("Name",&name_checksum)) // { // if ( name_checksum == checksum ) // { // return i; // } // } // } return -1; } int FindNamedNode(const char *p_name) { return FindNamedNode(Crc::GenerateCRCFromString(p_name)); } // Added for use by the NodeExists script function bool NodeExists(uint32 checksum) { return ( FindNamedNode( checksum, true ) == -1 ) ? false : true; // // Get the NodeArray // CArray *p_node_array=GetArray(0xc472ecc5/*NodeArray*/); // Dbg_MsgAssert(p_node_array,("CreateNodeNameHashTable could not find NodeArray")); // // // Scan through each node, getting it's name, and if it has a name add it in to the hash table. // for (uint32 i=0; iGetSize(); ++i) // { // CStruct *p_node=p_node_array->GetStructure(i); // Dbg_MsgAssert(p_node,("NULL p_node")); // // uint32 name_checksum=0; // if (p_node->GetChecksum("Name",&name_checksum)) // { // if ( name_checksum == checksum ) // { // return true; // } // } // } // return false; } #else #define __OLD_HASH_TABLE__ 0 #define NODE_NAME_HASHBITS 13 #if __OLD_HASH_TABLE__ #define NODE_NAME_HASH_SIZE ((1<mpNext; Mem::Free(p_entry); p_entry=p_next; } sp_node_name_hash_table[i].mpNext=NULL; #endif sp_node_name_hash_table[i].mNameChecksum=0; sp_node_name_hash_table[i].mNodeIndex=0; } } // Given that a new NodeArray symbol has got loaded due to the loading of a qb containing it, // this will generate the node name hash table. void CreateNodeNameHashTable() { Dbg_MsgAssert(s_node_name_hash_table_initialised,("Node name hash table not initialised")); // Make sure the old one is cleared. ClearNodeNameHashTable(); // Get the NodeArray CArray *p_node_array=GetArray(0xc472ecc5/*NodeArray*/); Dbg_MsgAssert(p_node_array,("CreateNodeNameHashTable could not find NodeArray")); // Make sure we're using the script heap, cos the hash table is going to hang around // for the duration of the level. Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().ScriptHeap()); // Scan through each node, getting it's name, and if it has a name add it in to the hash table. for (uint32 i=0; iGetSize(); ++i) { CStruct *p_node=p_node_array->GetStructure(i); Dbg_MsgAssert(p_node,("NULL p_node")); uint32 name_checksum=0; if (p_node->GetChecksum("Name",&name_checksum)) { // Node i has name name_checksum, so add it in to the hash table. // A checksum of zero is used to indicate an empty hash table entry, // so check that the name checksum is not zero by chance. Dbg_MsgAssert(name_checksum,("Node has zero name checksum ?")); SNodeNameHashEntry *p_entry=&sp_node_name_hash_table[name_checksum&((1<mNameChecksum) { // p_entry is already occupied, so skip to the end of the list that starts at p_entry. while (p_entry->mpNext) { p_entry=p_entry->mpNext; } // p_entry is now the last entry in the list. // Create a new entry and fill it in. SNodeNameHashEntry *p_new=(SNodeNameHashEntry*)Mem::Malloc(sizeof(SNodeNameHashEntry)); p_new->mNameChecksum=name_checksum; p_new->mNodeIndex=i; p_new->mpNext=NULL; // Tag it onto the end of the list. p_entry->mpNext=p_new; } else { // p_entry is free, so stick the info in there. p_entry->mNameChecksum=name_checksum; p_entry->mNodeIndex=i; // Quick check for leaks. Dbg_MsgAssert(p_entry->mpNext==NULL,("p_entry->mpNext not NULL ??")); p_entry->mpNext=NULL; } #else // just skip forward until we find an empty entry while (p_entry->mNameChecksum) { p_entry++; // note in the following assertion we leave one entry to guarentee we have // a zero entry roadblock to stop searches // otherwise we have a slight possibility of very obscure bugs Dbg_MsgAssert(p_entry < &sp_node_name_hash_table[NODE_NAME_HASH_SIZE-1],("Hash table overflow")); } p_entry->mNameChecksum=name_checksum; p_entry->mNodeIndex=i; #endif } } Mem::Manager::sHandle().PopContext(); } // Searches the node array for a node whose Name is the passed checksum. int FindNamedNode(uint32 checksum, bool assert) { // TODO: Fix bug where this returns 0 when a checksum of 0 is passed. It should // not find a node in that case. Dbg_MsgAssert(s_node_name_hash_table_initialised,("Node name hash table not initialised")); SNodeNameHashEntry *p_entry=&sp_node_name_hash_table[checksum&((1<mNameChecksum!=checksum) { p_entry=p_entry->mpNext; } if (assert) { Dbg_MsgAssert(p_entry,("No node with name %s found.",FindChecksumName(checksum))); } if (!p_entry) { return -1; } #else while (p_entry->mNameChecksum && p_entry->mNameChecksum!=checksum) { p_entry++; } if (assert) { Dbg_MsgAssert(p_entry->mNameChecksum,("No node with name %s found.",FindChecksumName(checksum))); } if (!p_entry->mNameChecksum) { return -1; } #endif return p_entry->mNodeIndex; } int FindNamedNode(const char *p_name) { return FindNamedNode(Crc::GenerateCRCFromString(p_name)); } // Added for use by the NodeExists script function bool NodeExists(uint32 checksum) { Dbg_MsgAssert(s_node_name_hash_table_initialised,("Node name hash table not initialised")); SNodeNameHashEntry *p_entry=&sp_node_name_hash_table[checksum&((1<mNameChecksum!=checksum) { p_entry=p_entry->mpNext; } if (p_entry) { return true; } #else while (p_entry->mNameChecksum && p_entry->mNameChecksum!=checksum) { p_entry=p_entry++; } if (p_entry->mNameChecksum) { return true; } #endif return false; } #endif // __PLAT_NGC__ //////////////////////////////////////////////////////////////////// // // Node name prefix stuff. // //////////////////////////////////////////////////////////////////// // Linked list stuff, used when generating the node lists. // These only exist temporarily in memory. struct STempNode { int mNodeIndex; STempNode *mpNext; }; // MEMOPT 800K TEMP Size of temporary buffer used when generating prefix info. #define NUM_TEMP_NODES 120000 static STempNode *sp_temp_nodes=NULL; static STempNode *sp_free_temp_nodes=NULL; // The lookup table, which exists in memory for the duration of the level. struct SPrefixLookup { // Checksum of the prefix string uint32 mChecksum; // Pointer to the list of node indices of the nodes that have this prefix. union { // Points to somewhere inside sp_node_list_buffer // pNodeList[0] contains the number of nodes. uint16 *mpNodeList; // Points to a temporary linked list of nodes, which is used for speed when generating the prefix info. // (Uses up too much memory to keep in memory all the time) STempNode *mpTempNodesHeadPointer; }; }; // MEMOPT 80K PERM An array of SPrefixLookup's, one for each possible prefix. // This array is kept in order of checksum, smallest checksum first, to enable quick searching // using a binary search. #define MAX_PREFIX_LOOKUPS 13000 static SPrefixLookup sp_prefix_lookups[MAX_PREFIX_LOOKUPS]; static uint32 s_num_prefix_lookups=0; // MEMOPT 200K PERM Big array of uint16's for holding the node lists. #define NODE_LIST_BUFFER_SIZE 121000 static uint32 s_node_list_buffer_used=0; static uint16 sp_node_list_buffer[NODE_LIST_BUFFER_SIZE]; // Does a binary search of the lookup table, and returns the index of the entry // that has the passed checksum. // If there is no matching entry, it will return the index of the entry with the next smallest // checksum. // If there is no matching entry, and the passed checksum is smaller than the smallest checksum in the // table, then it will return 0. static uint32 sFindPrefixLookup(uint32 checksum) { Dbg_MsgAssert(s_num_prefix_lookups,("Zero s_num_prefix_lookups")); uint32 bottom=0; uint32 top=s_num_prefix_lookups-1; uint32 middle=(bottom+top)>>1; while (bottom!=middle) { uint32 ch=sp_prefix_lookups[middle].mChecksum; if (ch==checksum) { return middle; } else if (checksum>1; } if (sp_prefix_lookups[top].mChecksum > checksum) { return bottom; } return top; } // Creates the big temporary array of nodes. static void sCreateTempNodes() { Dbg_MsgAssert(sp_temp_nodes==NULL,("sp_temp_nodes not NULL ?")); sp_temp_nodes=(STempNode*)Mem::Malloc(NUM_TEMP_NODES*sizeof(STempNode)); Dbg_MsgAssert(sp_temp_nodes,("Could not allocate sp_temp_nodes")); // Link them all into a free list. for (int i=0; impNext; return p_new; } // Converts the linked lists of nodes into simple arrays, kept in the sp_node_list_buffer static array. // Uses up less memory. Also means the rest of the game code that calls GetPrefixInfo does not have // to be changed. static void sConvertTempNodes() { Dbg_MsgAssert(s_node_list_buffer_used==0,("Expected s_node_list_buffer_used to be 0")); uint16 *p_buf=sp_node_list_buffer; // For each prefix in the lookup table ... for (uint32 i=0; impNext; } // Check there is enough space to copy the node list into. Dbg_MsgAssert(s_node_list_buffer_used+1+count<=NODE_LIST_BUFFER_SIZE,("Node list buffer overflow")); // Make the prefix entry now point to the simple array. sp_prefix_lookups[i].mpNodeList=p_buf; // First entry is the count. *p_buf++=count; // Copy in all the node indices. p_node=p_first; while (p_node) { *p_buf++=p_node->mNodeIndex; p_node=p_node->mpNext; } // Update the space used. The +1 is for the count. s_node_list_buffer_used+=1+count; } // Dbg_MsgAssert(0,("buffer = %d\n",s_node_list_buffer_used)); } // Adds a new node index to a particular prefix checksum's entry in the lookup table. static void sAddNewPrefix(uint32 checksum, int nodeIndex) { // sFindPrefixLookup will assert if s_num_prefix_lookups is zero, so do this // as a special case for the very first one added. if (s_num_prefix_lookups==0) { STempNode *p_new=sNewTempNode(); p_new->mNodeIndex=nodeIndex; p_new->mpNext=NULL; // Stick it in at index 0. The array will be maintained as a sorted // array from now on. sp_prefix_lookups[0].mChecksum=checksum; sp_prefix_lookups[0].mpTempNodesHeadPointer=p_new; ++s_num_prefix_lookups; // All done. return; } // Look up the checksum, which may not be there. int index=sFindPrefixLookup(checksum); uint32 ch=sp_prefix_lookups[index].mChecksum; // ch may or may not be checksum. If it isn't, it will either be the next smallest one in the // array, or it will be the checksum at index 0 in the case of checksum being smaller than all of them. if (ch==checksum) { // This prefix is already in the array, so just have to add the new node index to // its list. STempNode *p_new=sNewTempNode(); p_new->mNodeIndex=nodeIndex; p_new->mpNext=sp_prefix_lookups[index].mpTempNodesHeadPointer; sp_prefix_lookups[index].mpTempNodesHeadPointer=p_new; } else { // The prefix is not in the table, so we have to add it. Dbg_MsgAssert(s_num_prefix_lookupsch) { // The above if will mostly be true, because usually Ch will be the next smaller checksum // in the table. Hence we increment index so that all the checksums above ch get shifted up, // so that the new checksum can be inserted at index, maintaining the sort order. ++index; } else { // The above if will be false in the case of checksum being smaller than all the current checksums // in the table. In this case, all the checksums need to be shifted up and checksum inserted at the // bottom, so index should be zero here. Dbg_MsgAssert(index==0,("index not zero ?")); } // Shift everything up one so that the new checksum can be inserted. for (int i=s_num_prefix_lookups; i>index; --i) { sp_prefix_lookups[i]=sp_prefix_lookups[i-1]; } // Insert the new checksum, and make a new list for it with just one entry at the moment. STempNode *p_new=sNewTempNode(); p_new->mNodeIndex=nodeIndex; p_new->mpNext=NULL; sp_prefix_lookups[index].mChecksum=checksum; sp_prefix_lookups[index].mpTempNodesHeadPointer=p_new; // Increment the count. ++s_num_prefix_lookups; } } void DeletePrefixInfo() { // Just to be sure. sDeleteTempNodes(); s_node_list_buffer_used=0; s_num_prefix_lookups=0; } // This is called from LoadQB if the QB contains a NodeArray. void GeneratePrefixInfo() { DeletePrefixInfo(); // Create the temporary array of nodes used to make linked lists in sAddNewPrefix, for speed. sCreateTempNodes(); // Scan through all the nodes, look up their names, and add all their possible // prefixes to the lookup table. CArray *p_node_array=GetArray(0xc472ecc5/*NodeArray*/); Dbg_MsgAssert(p_node_array,("NodeArray not found")); for (uint32 i=0; iGetSize(); ++i) { CStruct *p_node=p_node_array->GetStructure(i); Dbg_MsgAssert(p_node,("NULL p_node")); uint32 name_checksum=0; p_node->GetChecksum("Name",&name_checksum); if (name_checksum) { // Search the hash table for the name. const char *p_name=GetChecksumNameFromLastQB(name_checksum); if (p_name) { // Add all the possible prefixes to the lookup table. int string_length=strlen(p_name); for (int j=1; j<=string_length; ++j) { sAddNewPrefix(Crc::GenerateCRC(p_name,j),i); } } } } // Move the info in the linked lists into the more memory efficient static array. sConvertTempNodes(); // Remove the temporary stuff. sDeleteTempNodes(); #ifdef __NOPT_ASSERT__ // Quick check to make sure the lookup table seems right. if (s_num_prefix_lookups) { for (uint32 i=0; iGetStructure(*p_nodes); Mth::Vector node_pos; p_node->GetVector(CRCD(0x7f261953,"pos"),&node_pos); float node_dist = (node_pos - pos).LengthSqr(); //printf ("Node %d at (%.02f, %.02f, %.02f) dist %.02f\n",*p_nodes,node_pos[X],node_pos[Y],node_pos[Z],node_dist); if (node_dist < closest_dist) { // printf ("closest node %d\n",*p_nodes); closest_dist = node_dist; closest_node = *p_nodes; } p_nodes++; num_nodes--; } return closest_node; } // return the index of the node nearest to this position that // matches this prefix int GetNearestNodeByPrefix(const char *p_prefix, const Mth::Vector &pos) { return GetNearestNodeByPrefix(Crc::GenerateCRCFromString(p_prefix),pos); } //////////////////////////////////////////////////////////////////// // // Utility functions for getting nodes, links, etc // //////////////////////////////////////////////////////////////////// CStruct *GetNode(int nodeIndex) { CArray *p_node_array=GetArray(0xc472ecc5/*NodeArray*/); Dbg_MsgAssert(p_node_array,("NodeArray not found")); CStruct *p_node=p_node_array->GetStructure(nodeIndex); Dbg_MsgAssert(p_node,("NULL p_node")); return p_node; } // This is a DEPRECATED function // only in to support the rather odd manner of access in nodes via index uint32 GetNodeNameChecksum(int nodeIndex) { CStruct *p_struct = GetNode(nodeIndex); uint32 name = 0; p_struct->GetChecksum(CRCD(0xa1dc81f9,"name"),&name); return name; } uint32 GetNumLinks(CStruct *p_node) { Dbg_MsgAssert(p_node,("NULL p_node")); CArray *p_links=NULL; p_node->GetArray(0x2e7d5ee7/*Links*/,&p_links); if (p_links==NULL) { return 0; } return p_links->GetSize(); } uint32 GetNumLinks(int nodeIndex) { CArray *p_node_array=GetArray(0xc472ecc5/*NodeArray*/, NO_ASSERT); if (p_node_array) { CStruct *p_node=p_node_array->GetStructure(nodeIndex); Dbg_MsgAssert(p_node,("NULL p_node")); return GetNumLinks(p_node); } return 0; } int GetLink(CStruct *p_node, int linkNumber) { Dbg_MsgAssert( p_node, ( "NULL p_node" ) ); CArray *p_links=NULL; p_node->GetArray( CRCD( 0x2e7d5ee7, "Links" ), &p_links); Dbg_MsgAssert( p_links, ( "Tried to call GetLink when there are no links" ) ); Dbg_MsgAssert( p_links->GetSize(), ( "Tried to call GetLink when there are no links (empty Links array)" ) ); return p_links->GetInteger( linkNumber ); } int GetLink(int nodeIndex, int linkNumber) { return GetLink(GetNode(nodeIndex),linkNumber); } // return true if Node1 is linked to Node2 bool IsLinkedTo(int node1, int node2) { int n = GetNumLinks(node1); for (int i = 0; iGetVector(CRCD(0xb9d31b0a,"Position"),p_vector,Script::NO_ASSERT) ) { uint32 name = 0; p_node->GetChecksum(CRCD(0xa1dc81f9,"name"),&name); if (name) { Dbg_Message( "Warning: 'Position' deprecated! reexport, node %s",Script::FindChecksumName(name) ); } else { Dbg_Message( "Warning: 'Position' deprecated! reexport" ); } } #endif if ( p_node->GetVector(CRCD(0x7f261953,"pos"),p_vector,Script::NO_ASSERT) ) { // no need to negate the new style vectors } else if ( p_node->GetVector(CRCD(0xb9d31b0a,"Position"),p_vector,Script::NO_ASSERT) ) { (*p_vector)[Z]=-(*p_vector)[Z]; } else { *p_vector = Mth::Vector(0.0f,0.0f,0.0f,1.0f); // Script::PrintContents( p_node ); // Dbg_MsgAssert( 0, ( "No 'pos' vector found in node" ) ); } } void GetPosition(int nodeIndex, Mth::Vector *p_vector) { Dbg_MsgAssert(p_vector,("NULL p_vector")); GetPosition(GetNode(nodeIndex),p_vector); } void GetOrientation(CStruct *p_node, Mth::Matrix *p_matrix) { Dbg_MsgAssert(p_node,("NULL p_node")); Dbg_MsgAssert(p_matrix,("NULL p_matrix")); Mth::Vector orientation_vector; if ( p_node->GetVector(CRCD(0xc97f3aa9, "orientation"),&orientation_vector,Script::NO_ASSERT) ) { // assumes a positive scalar component Mth::Quat orientation_quat; orientation_quat.SetVector(orientation_vector); orientation_quat.SetScalar(sqrtf(1.0f - orientation_vector.LengthSqr())); orientation_quat.GetMatrix(*p_matrix); } else { p_matrix->Identity(); } } void GetOrientation(int nodeIndex, Mth::Vector *p_matrix) { Dbg_MsgAssert(p_matrix,("NULL p_matrix")); GetPosition(GetNode(nodeIndex),p_matrix); } void GetAngles(CStruct *p_node, Mth::Vector *p_vector) { Dbg_MsgAssert(p_node,("NULL p_node")); Dbg_MsgAssert(p_vector,("NULL p_vector")); // Make sure they are initialised to zero, in case there is no Angles component. // Angles components of (0,0,0) are ommitted to save memory. p_vector->Set(); p_node->GetVector(0x9d2d0915/*Angles*/,p_vector); } void GetAngles(int nodeIndex, Mth::Vector *p_vector) { Dbg_MsgAssert(p_vector,("NULL p_vector")); GetAngles(GetNode(nodeIndex),p_vector); } CArray *GetIgnoredLightArray(CStruct *p_node) { Dbg_MsgAssert(p_node,("NULL p_node")); CArray *p_ignored_lights=NULL; p_node->GetArray(0xb7b030be/*IgnoredLights*/,&p_ignored_lights); return p_ignored_lights; } CArray *GetIgnoredLightArray(int nodeIndex) { return GetIgnoredLightArray(GetNode(nodeIndex)); } // Used by Ryan in the Park Editor. // This creates an array (called NodeArray) of Size structures, with all the entries initialised to empty structures. void CreateNodeArray(int size, bool hackUseFrontEndHeap) { // copied from Script::Cleanup() //KillStoppedScripts(); //RemoveOldTriggerScripts(); //RemoveSymbol(GenerateCRC("TriggerScripts")); CleanUpAndRemoveSymbol(CRCD(0xc472ecc5,"NodeArray")); DeletePrefixInfo(); ClearNodeNameHashTable(); // Make sure it doesn't exist already. // ParseQB will catch this anyway, but may as well check before getting there. Dbg_MsgAssert(GetArray(CRCD(0xc472ecc5,"NodeArray"))==NULL,("Called CreateNodeArray when a NodeArray already exists")); // Create a dummy QB file, and parse it the usual way. if (hackUseFrontEndHeap) Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().FrontEndHeap()); // Use the temporary heap. else Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap()); // Use the temporary heap. uint8 *p_dummy_qb=(uint8*)Mem::Malloc((1 // ESCRIPTTOKEN_NAME +4 // Checksum of 'NodeArray' +1 // ESCRIPTTOKEN_EQUALS +1 // ESCRIPTTOKEN_STARTARRAY +2*size // ESCRIPTTOKEN_STARTSTRUCT,ESCRIPTTOKEN_ENDSTRUCT pairs +1 // ESCRIPTTOKEN_ENDARRAY +1 // ESCRIPTTOKEN_ENDOFFILE )*sizeof(uint8)); uint8 *p_qb=p_dummy_qb; *p_qb++=ESCRIPTTOKEN_NAME; *p_qb++=0xc5; // Checksum of 'NodeArray' *p_qb++=0xec; *p_qb++=0x72; *p_qb++=0xc4; *p_qb++=ESCRIPTTOKEN_EQUALS; *p_qb++=ESCRIPTTOKEN_STARTARRAY; for (int i=0; imGotReloaded=false; Obj::InsertGoalEditorNodes(); Mem::Free(p_dummy_qb); Mem::Manager::sHandle().PopContext(); } //////////////////////////////////////////////////////////////////// // // Given a script, and the checksum of a function to look for, // this will search through the script for any calls to that function. // It will recursively search through any script calls. // The callback function is called for each occurrence of the function // call found, and the parameter list for that call is also passed. // // NOTE: Don't store the passed CStruct pointer! // It's a local variable in FindReferences. // To store the parameters create a new CStruct and use the // AppendStructure member function to copy in the contents of the // passed one. // //////////////////////////////////////////////////////////////////// // Note: p_args contains the set of parameters that were passed to scriptToScanThrough. It is passed so that // the <,> syntax can be evaluated. This is necessary because sometimes the EndGap command is not directly // passed the text and score, they might get passed via the <...> syntax, for example see the // Gap_Gen_End script in sk4_scripts.q static void FindReferences(uint32 scriptToScanThrough, uint32 functionToScanFor, void (*p_callback)(Script::CStruct *, const uint8 *), Script::CStruct *p_args, int Count) { // Don't recurse too deeply otherwise some levels take ages finding the gaps (eg, the secret level) if (Count > 5) { return; } Dbg_MsgAssert(p_callback,("NULL p_callback")); #ifdef __NOPT_ASSERT__ // Look up the function being scanned for to see if it is a cfunction. bool (*p_cfunction)(CStruct *pParams, CScript *pCScript)=NULL; CSymbolTableEntry *p_function_entry=LookUpSymbol(functionToScanFor); if (p_function_entry && p_function_entry->mType==ESYMBOLTYPE_CFUNCTION) { p_cfunction=p_function_entry->mpCFunction; } Dbg_MsgAssert(p_cfunction==NULL,("Cannot use FindReferences to find cfunction calls yet ...")); #endif #ifdef NO_SCRIPT_CACHING // Look up the script CSymbolTableEntry *p_script_entry=LookUpSymbol(scriptToScanThrough); if (!p_script_entry) { Dbg_Warning("Script '%s' not found in call to FindReferences",FindChecksumName(scriptToScanThrough)); return; } Dbg_MsgAssert(p_script_entry->mType==ESYMBOLTYPE_QSCRIPT,("'%s' is not a qscript",FindChecksumName(scriptToScanThrough))); // Get a pointer to it. uint8 *p_token=p_script_entry->mpScript; Dbg_MsgAssert(p_token,("NULL p_token ???")); // Skip over the 4-byte contents checksum. p_token+=4; #else Script::CScriptCache *p_script_cache=Script::CScriptCache::Instance(); Dbg_MsgAssert(p_script_cache,("NULL p_script_cache")); uint8 *p_script=p_script_cache->GetScript(scriptToScanThrough); Dbg_MsgAssert(p_script,("Script %s not found in script cache",Script::FindChecksumName(scriptToScanThrough))); uint8 *p_token=p_script; #endif // Create a structure for holding parameters. CStruct *p_params=new CStruct; // Skip to the first line of the script. p_token=SkipToStartOfNextLine(p_token); // Now scan through each token of the script in turn, looking for any calls to functionToScanFor. bool finished=false; while (!finished) { switch (*p_token) { case ESCRIPTTOKEN_RUNTIME_CFUNCTION: { // Skip over CFunction calls for now. p_token+=5; p_token=SkipToStartOfNextLine(p_token); break; } case ESCRIPTTOKEN_NAME: case ESCRIPTTOKEN_RUNTIME_MEMBERFUNCTION: { // Remember the location for passing to the callback. const uint8 *p_location=p_token; ++p_token; uint32 name_checksum=Read4Bytes(p_token).mChecksum; p_token+=4; // Skip over lines of script that are setting parameters if (*p_token==ESCRIPTTOKEN_EQUALS) { break; } CSymbolTableEntry *p_entry=Resolve(name_checksum); if (p_entry) { switch (p_entry->mType) { case ESYMBOLTYPE_CFUNCTION: Dbg_MsgAssert(p_entry->mpCFunction,("\nLine %d of %s\nNULL pCFunction",GetLineNumber(p_token),FindChecksumName(p_entry->mSourceFileNameChecksum))); if (name_checksum==functionToScanFor) { // Found the required function call! Get the params and call the callback. p_params->Clear(); // Note: Uses p_args to look up any params enclosed in <,> that are encountered. p_token=AddComponentsUntilEndOfLine(p_params,p_token,p_args); //printf("Callback: %s\n",FindChecksumName(scriptToScanThrough)); (*p_callback)(p_params,p_location); } else { p_token=SkipToStartOfNextLine(p_token); } break; case ESYMBOLTYPE_MEMBERFUNCTION: if (name_checksum==functionToScanFor) { // Found the required function call! Get the params and call the callback. p_params->Clear(); // Note: Uses p_args to look up any params enclosed in <,> that are encountered. p_token=AddComponentsUntilEndOfLine(p_params,p_token,p_args); //printf("Callback: %s\n",FindChecksumName(scriptToScanThrough)); (*p_callback)(p_params,p_location); if (name_checksum==0xe5399fb2) // EndGap { uint32 GapScript=0; if (p_params->GetChecksum("GapScript",&GapScript)) { // Passing NULL for p_args here because it is not clear what parameters are // going to be passed to the GapScript when it is run. FindReferences(GapScript,functionToScanFor,p_callback,NULL,Count+1); } } } else { p_token=SkipToStartOfNextLine(p_token); } break; case ESYMBOLTYPE_QSCRIPT: p_params->Clear(); // Note: Uses p_args to look up any params enclosed in <,> that are encountered. p_token=AddComponentsUntilEndOfLine(p_params,p_token,p_args); if (name_checksum==functionToScanFor) { // Found the required function call! Get the params and call the callback. //printf("Callback: %s\n",FindChecksumName(scriptToScanThrough)); (*p_callback)(p_params,p_location); } else { p_token=SkipToStartOfNextLine(p_token); } // It's a q-script, so recursively search that too. // Passing the just calculated p_params so that the <,> syntax within the script // can be evaluated. FindReferences(name_checksum,functionToScanFor,p_callback,p_params,Count+1); break; default: break; } } break; } case ESCRIPTTOKEN_KEYWORD_REPEAT: p_token=SkipToken(p_token); // Scan over any repeat parameters. p_token=SkipToStartOfNextLine(p_token); break; case ESCRIPTTOKEN_KEYWORD_IF: p_token=SkipToken(p_token); if (*p_token==ESCRIPTTOKEN_KEYWORD_NOT) { p_token=SkipToken(p_token); } // If the if keyword is followed by an open parenthesis, then it must be using an // expression. Often the expression may contain c-function calls, such as: // if ( (GotParam Foo) | (GotParam Blaa) ) // This was causing a problem because this loop would skip over the open parenthesis, // get to the GotParam, and then recognizing that GotParam is a cfunction it would // try to skip to the next line, but then that would cause a parenthesis mismatch // assert in SkipToStartOfNextLine because the open parenth had been skipped over. // So to get around that, skip to the next line as soon as the open parenth is detected. if (*p_token==ESCRIPTTOKEN_OPENPARENTH) { p_token=SkipToStartOfNextLine(p_token); } break; case ESCRIPTTOKEN_KEYWORD_ENDSCRIPT: finished=true; break; default: p_token=SkipToken(p_token); break; } } #ifndef NO_SCRIPT_CACHING p_script_cache->DecrementScriptUsage(scriptToScanThrough); #endif // Delete the temporary p_params. delete p_params; } void ScanNodeScripts(uint32 componentName, uint32 functionName, void (*p_callback)(CStruct *, const uint8 *)) { Script::DisableExpressionEvaluatorErrorChecking(); CArray *p_node_array=GetArray(0xc472ecc5); // Checksum of NodeArray Dbg_MsgAssert(p_node_array,("NodeArray not found")); for (uint32 i=0; iGetSize(); ++i) { CStruct *p_node=p_node_array->GetStructure(i); Dbg_MsgAssert(p_node,("NULL p_node")); uint32 script_checksum=0; if (p_node->GetChecksum(componentName,&script_checksum)) { FindReferences(script_checksum,functionName,p_callback,NULL,0); } } Script::EnableExpressionEvaluatorErrorChecking(); } } // namespace SkateScript