Open Prim Animator/Notecard Import

//! @brief Imports a set of animation frames from a notecard
/**
* @author SignpostMarv Martin
*/
//! name of notecard to parse
string notecard = "Open Prim Animator: Import";
//! inventory key of notecard
key _notecard;
//! Temporarily stores the entire notecard contents
string contents;
//! Since llGetNotecardLine() only reads the first 255 bytes of a notecard line, we need to temporarily store the notecard contents in a list before dumping it to the contents string.
list _contents;
//! Whilst not truly multi-threaded, this script attempts non-sequential loading of the notecard (as opposed to sequential loading), theoretically providing some small performance gain
list multithread_fetch = [];
//! dataserver query variable used to determine how many lines are in a notecard- needed to pre-populate multithread_fetch to the correct size.
key nlq;
//! import command
key import = "6b78fcc8-e147-4105-99a6-ff19b4bf559d";
//! export command
key export = "7c2ca168-2b64-4836-8727-8e62b78dbd44";
//! command for updating the root scale in the core OPA script
key op_alter_rootScale = "f9d3389e-a78c-43f8-9e35-c11adec112a5";
//! command for restoring link order
key op_adjust_link_order_by_names = "e907917f-e5a3-490e-aaac-4596a0dc176d";
//! temporary store of link names
list names;
//! temporary store of animation frame positions
list pos;
//! temporary store of animation frame rotations
list rot;
//! temporary store of animation frame sizes
list size;
//! temporary store of root scale. This is needed in case a new animation was generated on an object with a different root scale to the prim the object the notecard is being imported to
vector rootScale;
//! I think this is here solely due to copypasta.
export_string(string _export_string){
 integer s=1000;
 while(llStringLength(_export_string) > s){
 llOwnerSay(llGetSubString(_export_string,0,(s-1)));
 _export_string = llGetSubString(_export_string,s,-1);
 }
 if(llStringLength(_export_string) > 0){
 llOwnerSay(_export_string);
 }
}
default{
 state_entry(){
 if(llGetInventoryType(notecard) == INVENTORY_NOTECARD){ // if the specified notecard is in the object,
 _notecard = llGetInventoryKey(notecard); // get the inventory key
 state num_lines; // then attempt to get the number of lines in the notecard
 }
 }
 changed(integer c){
 if(c & CHANGED_INVENTORY){ // if the inventory has changed (e.g. if the notecard *wasn't* in the object),
 llResetScript(); // restart the script
 }
 }
}
state num_lines{ // there should probably be a CHANGED_INVENTORY check in case the scripts are running reaaaaallllyyyy slow.
 state_entry(){
 nlq = llGetNumberOfNotecardLines(notecard); // attempt to get the number of lines in the notecard
 }
 dataserver(key q, string m){
 if(q == nlq){
 integer i;
 integer j = (integer)m;
 list k = []; // declaring list k = []; isn't exactly necessary, you could do list k;
 list l = [];
 for(i=0;i<j;++i){ // since we don't have the concept of transactions in LSL scripts, we're going to build up these lists separately 
 k += [NULL_KEY];
 l += [NULL_KEY];
 }
 _contents = k; // this is where we do the atomic writes due to lack of transactions on variables
 multithread_fetch = l; // this is where we do the atomic writes due to lack of transactions on variables
 k = []; // cleaning up the local variable on purpose in case the LSL VM executing this script is inefficient
 l = []; // cleaning up the local variable on purpose in case the LSL VM executing this script is inefficient
 state mtf; // now we've pre-populated the lists to the correct size, start the "multi-threaded fetch" of notecard contents.
 }
 }
}
state mtf{
 state_entry(){
 integer i;
 integer j=llGetListLength(multithread_fetch); // see why we pre-populated it now ? :D
 for(i=0;i<j;++i){ // the reason we pre-populate rather than building the list here is to try and keep the level of consumed memory more consistent in this busy state, so the memory consumption doesn't steadily increase over time but regularly plateaus instead.
 multithread_fetch = llListReplaceList(multithread_fetch,[llGetNotecardLine(notecard,i)],i,i); // sets the notecard fetching away
 }
 }
 dataserver(key q, string m){
 integer i = llListFindList(multithread_fetch,[q]); // get the position of the dataserver query id 
 if(i >= 0){
 _contents = llListReplaceList(_contents,[m],i,i); // the traditional method of notecard fetching in LSL is single-threaded, getting one line at a time
 }
 if(llListFindList(_contents,[NULL_KEY]) == -1){ // since we started out with a NULL_KEY-filled list, if there aren't any NULL_KEY in the list then we know it's done. caveat scriptor: since key and string types are interchangeable 
 contents = llDumpList2String(_contents,"");
 state done_reading;
 }
 }
 state_exit(){ // do some cleanup in the event the LSL VM executing this script is silly.
 _contents = [];
 multithread_fetch = [];
 }
}
state done_reading{
 state_entry(){
 integer rs_pos = llSubStringIndex(contents,"rootscale:"); // get the string index for the rootscale block
 integer names_pos = llSubStringIndex(contents,"names:"); // get the string index for the names block
 integer pos_pos = llSubStringIndex(contents,"pos:"); // get the string index for the position block
 integer rot_pos = llSubStringIndex(contents,"rot:"); // get the string index for the rotation block
 integer size_pos = llSubStringIndex(contents,"size:"); // get the string index for the size block
 rootScale = (vector)llGetSubString(contents, rs_pos + 10, names_pos - 1); // get the root scale from the notecard
 names = llParseString2List(llGetSubString(contents,names_pos + 6,pos_pos - 1),["|"],[]); // Fun Fact! Support for correcting link order was added in May 2011 but wasn't actually implemented until November 2011!
 pos = llParseString2List(llGetSubString(contents,pos_pos + 4,rot_pos - 1),["|"],[]);
 rot = llParseString2List(llGetSubString(contents,rot_pos + 4,size_pos - 1),["|"],[]);
 size = llParseString2List(llGetSubString(contents,size_pos + 5,-1),["|"],[]);
 contents = ""; // clearing memory up, we don't need the string in memory now.
 integer a = llGetListLength(names);
 integer b = llGetListLength(pos);
 if(b % a || llGetListLength(rot) % a || llGetListLength(size) % a){ // since the number of names corresponds to the number of prims, we can use modulus to ensure that the notecard doesn't have a corrupt export.
 llOwnerSay("List Lengths do not match, possible corrupted notecard (" + llList2CSV([a,llGetListLength(pos),llGetListLength(rot),llGetListLength(size)]) + ")");
 }else{
 llMessageLinked(LINK_THIS,b,"XDimportLength",NULL_KEY); // notecard contents are valid, core script can start import now.
 llOwnerSay("Do import now");
 }
 }
 changed(integer c){
 if(c & CHANGED_INVENTORY && llGetInventoryKey(notecard) != _notecard){
 llResetScript();
 }
 }
 link_message(integer s, integer n, string m, key k){
 if(m == "XDimportLength" && n == -1){
 integer i;
 integer j=llGetListLength(pos); // we've already verified that pos, rot and size are of the appropriate length so we only need to get the length of one of these.
 integer l;
 integer namesLength=llGetListLength(names);
 for(i=0;i<j;++i){
 l = llListFindList(names, [llGetLinkName(2 + (i % namesLength))]) + (llFloor(i / namesLength) * namesLength); // this is to fix any bugs caused by link order changes (finally added in November 2011)
 llMessageLinked(s,i,llDumpList2String([llList2Vector(pos,l),llList2Rot(rot,l),llList2Vector(size,l)],"|"),import); // send the import string to the core OPA script
 }
 llMessageLinked(s,0,(string)rootScale, op_alter_rootScale); // once we're done importing the position, rotation & size values for the animation frames, update the root scale to the value used in the creation of the animation frames.
 llOwnerSay("Done importing");
 }
 }
 touch_start(integer t){
 llMessageLinked(LINK_THIS,0,"XDmenu",NULL_KEY); 
 }
}
Notecard Import