Jump to content

Frionil Fang

Resident
  • Posts

    392
  • Joined

Everything posted by Frionil Fang

  1. A very simple way to preserve multiple objects' relative positioning is to select all of them (hold shift+click with build tool open, or drag a frame around them with build tool) and then just pick them up. They become a "coalesced object" asset in your inventory: coalesced objects can't be attached but when rezzed they contain all the individual objects just the way you picked them up. This won't of course help you getting them in the exact original position, but moving a bundle that's already relatively correct to each other is a bit easier.
  2. llShout is not region wide, the range is 100 meters, including the third dimension. You can use https://wiki.secondlife.com/wiki/LlRegionSay instead which is region wide.
  3. The midway point of your alpha mask reads as about 176 for me, so very much not 127. If use my image editor's levels tool to apply a gamma of 1.8 to your gradient, it becomes roughly linear with 127 around the middle. I'd imagine the issue is color profiles: Photoshop is applying some kind of gamma correction to your alpha mask gradient instead of treating it linearly. As for how to make it not do that, no idea, it's been decades since I used Photoshop, sorry. Color profiles can be damaging to image data that's not meant to be interpreted as colors, though, such as alpha channels and normal maps, so there has to be some kind of a feature to work in a linear space.
  4. Not the most clean, but something like: integer temp = coins; integer platinum = temp/1000000; temp -= platinum*1000000; integer gold = temp/10000; temp -= gold*10000; integer silver = temp/100; temp -= silver*100; integer copper = temp; Basically, split off coins of certain value subdivision from the total, update the value, repeat until only lowest subdivision remains.
  5. The same method works almost everywhere in the viewer: [<url> title], for example [http://google.com my webzone] would get you a clickable "my webzone" link to Google. You can't see it as clickable yourself, though, since you have to be able to edit it.
  6. Additionally, if having empty parameters is useful (I don't know about your case), you can use llParseStringKeepNulls. Works identically to llParseString2List but, like the name suggests, it keeps empty pieces between 2 separators or spacers. E.g. llParseString2List("7::31", [":"], []) would return the list ["7", "31"], but llParseStringKeepNulls("7::31", [":"], []) would get you ["7", "", "31"], the "" indicating an empty string that was between the two :s.
  7. It's pretty easy to see there's some shallow copying and referencing going on, generate a 256-length list by duplicating a seed list: default { state_entry() { integer i; list a = [0]; for(i = 0; i < 8; ++i) a += a; llSleep(0.01); // ensure memory count is accurate llOwnerSay((string)llGetListLength(a)); // =256 llOwnerSay((string)llGetUsedMemory()); // =5044 } } As opposed to generating a 256-length list by adding individual values: default { state_entry() { integer i; list a; for(i = 0; i < 256; ++i) a += 0; llSleep(0.01); // ensure memory count is accurate llOwnerSay((string)llGetListLength(a)); // =256 llOwnerSay((string)llGetUsedMemory()); // =8100 } } I prefer to use the latter to be certain the reported memory use is reliable and stays static after rewriting pieces of the list, even if the initialization takes longer. Also you can use the former to see yourself going above 64k script memory used, at least briefly: default { state_entry() { integer i; list a = [0]; for(i = 0; i < 14; ++i) a += a; llSleep(0.01); // ensure memory count is accurate llOwnerSay((string)llGetListLength(a)); // =16384 llOwnerSay((string)llGetUsedMemory()); // =69556 llOwnerSay("how is this still running"); // might crash, might not, depends on if the engine catches on to being over limit on this execution frame } }
  8. The params in the call will get set just as fast. E.g. set 20 params with a single call and there is no difference in how the results appear, regardless of which SPP/SLPP/SLPPF you use.
  9. Not sure what happened for you, but I tested it out on typical lists and it should be ok, and since you already have a working solution it doesn't matter much! For completeness' sake though, this works for me: list testCam = [12,1, 8,.01, 9,.0, 7, 2.0, 6,.01, 1,<.0,.0,.75>, 11,.0, 22,0, 0,10.0, 5,.01, 10,.0, 21, 0]; llLinksetDataWrite("save", list2typedstring(testCam)); // ... list loaded_params = typedstring2list(llLinksetDataRead("save")); llOwnerSay(llList2CSV(loaded_params));
  10. Another alternative: amend the list with the entry types before dumping it into string. Separator needs changing if your list contains strings with semicolons. string list2typedstring(list l) { integer i = llGetListLength(l); while(~--i) l = llListInsertList(l, (list)llGetListEntryType(l, i), i); return llDumpList2String(l, ";"); } list typedstring2list(string s) { list l = llParseString2List(s, [";"], []); integer i = llGetListLength(l)+1; integer t; while(~(i-=2)) { t = llList2Integer(l, i-1); if(t == TYPE_INTEGER) l = llListReplaceList(l, (list)llList2Integer(l, i), i-1, i); else if(t == TYPE_FLOAT) l = llListReplaceList(l, (list)llList2Float(l, i), i-1, i); else if(t == TYPE_STRING) l = llListReplaceList(l, (list)llList2String(l, i), i-1, i); else if(t == TYPE_VECTOR) l = llListReplaceList(l, (list)((vector)llList2String(l, i)), i-1, i); else if(t == TYPE_ROTATION) l = llListReplaceList(l, (list)((rotation)llList2String(l, i)), i-1, i); else if(t == TYPE_KEY) l = llListReplaceList(l, (list)llList2Key(l, i), i-1, i); } return l; }
  11. I don't think you can rely on the text staying in order if there's no delay between sending the separate messages; most of the time it'll work, but I'm pretty sure ordered delivery is not guaranteed, so it's worth keeping an eye out for. As for removing the timestamp, no can do on the script side, but you could change the object name before sending messages and apply a newline to reduce the need for post-processing, like, say: string original_name = llGetObjectName(); // store object's real name llSetObjectName("info dump"); // set the name to something descriptive for sending while (i < max) { string text = "\n"+llList2String(lines, i); // prepend a newline llRegionSayTo(avatar, PUBLIC_CHANNEL, text); ++i; } llSetObjectName(original_name); // restore original name
  12. There's a bar with a ">" that separates the world map from the legend/search fields/etc. If you press it, the right hand side is hidden, the bar moves to the right edge and changes to "<". Click it again to return the fields to view.
  13. I've been writing lots of 6502 assembly for a SL project and yes, going backwards tends to be simpler. "loop: LDX #endvalue, do_stuff_with_x, DEX, BNE loop" is easier for a 1-N loop than "loop: LDX #1, do_stuff_with_x, INX, CPX #endvalue+1, BCC loop", no comparison required as the CPU is already aware it reached 0. Makes me all the more thankful I don't *have* to do that at the LSL side and can just for/while away. As much as I enjoy working with the restrictions of lowest level code, it makes my head hurt.
  14. I almost always have an initializer even if it's reduntant, just for legibility. As much as I love crafting mystical runes of code in the name of efficiency, it's worth it to be certain of the initial value on a glance, to me.
  15. Since it's touch based, https://wiki.secondlife.com/wiki/LlRegionSayTo on channel 0 works as a (mostly) non-throttled alternative to IMs.
  16. Preferences->Chat->Radar->choose the reports you want.
  17. PRIM_PROJECTOR is write only, so trying to get the parameters fails. You'd need to keep track of them some other way: just hardcoded inside the script or provided by the user somehow.
  18. A non-physics object that is moved inside an avatar (or a physics object) will result in the avatar from getting ejected from it, but if I had to guess that won't result in as "realistic" collision since there's no actual momentum, just a sudden overlap of hitboxes.
  19. One last revision! I wish we could edit the earlier posts. In a further discussion someone suggested using the built-in base64 functions and, uh, I couldn't give you a good explanation why I didn't. I was doing something wrong and not getting 4 chars per 24 bits, but since it was brought up again and I took another look, it clicked: llIntegerToBase64 works MSB first, so you have to shift the 24-bit value left by 8. This gets you the expected 4 chars followed by "AA==" which you can trim away before saving to linkset data, and don't even need to add it back when reading back with llBase64ToInteger -- even if the wiki says the results are "unpredictable" they really are not, any missing bits are considered 0 from what I can see. The value of course has be shifted back right by 8, which will result in sign extension, though this doesn't cause any difference for the individual byte values and they'll fit in the float just fine. The 0xffffff mask is there just to make the quaternions look the same before and after storage, even if their effective byte content hasn't changed. Performance is more or less identical using the built-in ones and the char-ord loop, but the built in functions reclaim some 500 bytes of code memory, and the buffers don't get cycled as often resulting in peak memory savings too, which is nice. // Serialize a qb_array into linkset data as modified base64 with the given name qb_serialize_b64(list array, string name) { integer i; integer j; quaternion cell; string save; string save_chunk_x; string save_chunk_y; string save_chunk_z; string save_chunk_s; integer array_length = array!=EMPTY_LIST; for(i = 0; i < array_length; ++i) { cell = llList2Rot(array, i); // chunking speeds things up save_chunk_x = llGetSubString(llIntegerToBase64((integer)cell.x<<8), 0, 3); save_chunk_y = llGetSubString(llIntegerToBase64((integer)cell.y<<8), 0, 3); save_chunk_z = llGetSubString(llIntegerToBase64((integer)cell.z<<8), 0, 3); save_chunk_s = llGetSubString(llIntegerToBase64((integer)cell.s<<8), 0, 3); save += save_chunk_x + save_chunk_y + save_chunk_z + save_chunk_s; } llLinksetDataWrite(name, save); } // Deserialize a modified base64 qb_array from linkset data list qb_deserialize_b64(string name) { integer i; integer j; list array; quaternion cell; integer value; integer component; string load = llLinksetDataRead(name); integer load_length = llStringLength(load); for(i = 0; i < load_length; i+=16) { // build 3 bytes value from 4 chars cell.x = (llBase64ToInteger(llGetSubString(load, i, i+3))>>8)&0xffffff; cell.y = (llBase64ToInteger(llGetSubString(load, i+4, i+7))>>8)&0xffffff; cell.z = (llBase64ToInteger(llGetSubString(load, i+8, i+11))>>8)&0xffffff; cell.s = (llBase64ToInteger(llGetSubString(load, i+12, i+15))>>8)&0xffffff; array += cell; } return array; }
  20. Someone mentioned unrolling the small for-if-else loops in the (de)serialization, which is a good point that completely escaped me earlier. I've gotten into the habit of avoiding function calls with this project -- with LSL memory allocation being chunky it might not always matter, but often something like 4 calls to even built in functions can increase bytecode overhead compared to the for-if-else+one call approach. These unrolled+extra buffered versions will use somewhat more memory (about 1kB code, the buffers will have further impact after using the functions) but are faster, very rough numbers: hex serialization 10%, hex deserialization 50%(!), b64 serialization 25%, b64 deserialization 5%. // Serialize a qb_array into linkset data as hex with the given name qb_serialize_hex(list array, string name) { integer i; quaternion cell; string save; string save_chunk; integer array_length = array!=EMPTY_LIST; for(i = 0; i < array_length; ++i) { cell = llList2Rot(array, i); // chunking speeds things up save_chunk = hex((integer)cell.x, 6); save_chunk += hex((integer)cell.y, 6); save_chunk += hex((integer)cell.z, 6); save_chunk += hex((integer)cell.s, 6); save += save_chunk; } llLinksetDataWrite(name, save); } // Deserialize a hex-encoded qb_array from linkset data list qb_deserialize_hex(string name) { integer i; list array; quaternion cell; string load = llLinksetDataRead(name); string load_chunk; integer load_length = llStringLength(load); for(i = 0; i < load_length; i+=24) { // chunking speeds things up load_chunk = llGetSubString(load, i, i+23); cell.x = (integer)("0x"+llGetSubString(load_chunk, 0, 5)); cell.y = (integer)("0x"+llGetSubString(load_chunk, 6, 11)); cell.z = (integer)("0x"+llGetSubString(load_chunk, 12, 17)); cell.s = (integer)("0x"+llGetSubString(load_chunk, 18, 23)); array += cell; } return array; } // Serialize a qb_array into linkset data as modified base64 with the given name qb_serialize_b64(list array, string name) { integer i; integer j; quaternion cell; string save; string save_chunk_x; string save_chunk_y; string save_chunk_z; string save_chunk_s; integer array_length = array!=EMPTY_LIST; for(i = 0; i < array_length; ++i) { cell = llList2Rot(array, i); // chunking speeds things up save_chunk_x = save_chunk_y = save_chunk_z = save_chunk_s = ""; for(j = 18; j >= 0; j-=6) { save_chunk_x += llChar(0x3f+(((integer)cell.x>>j)&0x3f)); save_chunk_y += llChar(0x3f+(((integer)cell.y>>j)&0x3f)); save_chunk_z += llChar(0x3f+(((integer)cell.z>>j)&0x3f)); save_chunk_s += llChar(0x3f+(((integer)cell.s>>j)&0x3f)); } save += save_chunk_x + save_chunk_y + save_chunk_z + save_chunk_s; } llLinksetDataWrite(name, save); } // Deserialize a modified base64 qb_array from linkset data list qb_deserialize_b64(string name) { integer i; integer j; list array; quaternion cell; integer value; integer component; string load = llLinksetDataRead(name); string load_chunk_x; string load_chunk_y; string load_chunk_z; string load_chunk_s; integer load_length = llStringLength(load); for(i = 0; i < load_length; i+=16) { // build 3 bytes value from 4 chars // chunking speeds things up load_chunk_x = llGetSubString(load, i, i+3); load_chunk_y = llGetSubString(load, i+4, i+7); load_chunk_z = llGetSubString(load, i+8, i+11); load_chunk_s = llGetSubString(load, i+12, i+15); for(j = 0; j < 4; ++j) { cell.x = ((integer)cell.x<<6)|((llOrd(load_chunk_x, j)-0x3f)&0x3f); cell.y = ((integer)cell.y<<6)|((llOrd(load_chunk_y, j)-0x3f)&0x3f); cell.z = ((integer)cell.z<<6)|((llOrd(load_chunk_z, j)-0x3f)&0x3f); cell.s = ((integer)cell.s<<6)|((llOrd(load_chunk_s, j)-0x3f)&0x3f); } array += cell; cell = <0, 0, 0, 0>; } return array; }
  21. Tested the modified base64 serialization for linkset data: this saves a lot of space (instead of 12 bytes per quat taking 24 bytes of linkset data, 12 bytes encodes into 16), but it must use llOrd to read it back which requires a bit of extra buffering to not lose a lot of performance -- llOrd is very slow on long strings compared to llGetSubString. With the extra write/read buffers, the performance isn't too much lower than the hex approach. The base64 is modified in the sense that the character set is linear from 0x3f ("?") to 0x7e ("~") - not as "legible" as standard, but simple to work with. // Serialize a qb_array into linkset data as modified base64 with the given name qb_serialize_b64(list array, string name) { integer i; integer j; quaternion cell; integer value; integer component; string save; string save_chunk; integer array_length = array!=EMPTY_LIST; for(i = 0; i < array_length; ++i) { cell = llList2Rot(array, i); for(component = 0; component < 4; ++component) { if(component == 0) value = (integer)cell.x; else if(component == 1) value = (integer)cell.y; else if(component == 2) value = (integer)cell.z; else value = (integer)cell.s; save_chunk = ""; // building onto a smaller chunk saves some string op time for(j = 18; j >= 0; j-=6) save_chunk += llChar(0x3f+((value>>j)&0x3f)); save += save_chunk; } } llLinksetDataWrite(name, save); } // Deserialize a modified base64 qb_array from linkset data list qb_deserialize_b64(string name) { integer i; integer j; list array; quaternion cell; integer value; integer component; string load = llLinksetDataRead(name); string load_chunk; integer load_length = llStringLength(load); for(i = 0; i < load_length; i+=4) { // build 3 bytes value from 4 chars load_chunk = llGetSubString(load, i, i+3); // llOrd is very slow on long strings for(j = 0; j < 4; ++j) { value = (value<<6)|((llOrd(load_chunk, j)-0x3f)&0x3f); } component = (i>>2)&3; // order x-y-z-s if(component == 0) cell.x = value; else if(component == 1) cell.y = value; else if(component == 2) cell.z = value; else cell.s = value; if(component == 3 || i >= load_length-4) { // write on component 3, or last entry array += cell; cell = <0, 0, 0, 0>; // avoids junk data at last entry, if incomplete } value = 0; // must be reset } return array; } As a matter of curiosity, I ran some numbers on the serialization performance since I need to know that for my project: my byte arrays are 1024 bytes (so 86 quats) and I used a chunk-splitting implementation to find the optimal number of chunks, getting the average times from 100 iterations. More chunks means more wasted linkset data from the linkset names themselves, and there's going to be a lot of these 1024 byte arrays so it adds up. The numbers relevant to the serialization/deserialization functions in this post would be the ones with 1 chunk. You can see how horridly slow the unbuffered base64 version is, due to llOrd choking on long strings.
  22. A few functions to simulate byte arrays using quaternion lists. Quaternion lists have a good balance of performance/memory compared to integers (good speed, bad memory use) and strings (bad speed, good memory use). The arrays created are always rounded up to the nearest 12 bytes. Includes allocation, reading, writing, serializing and deserializing to/from linkset data. These are simplified versions of what I use myself (my own projects need more performance so they use hardcoded bounds, chunked approaches to keep list/string lengths short, global variables to avoid parameter passing, etc.) but in case someone needs byte arrays here's some ideas I suppose. The serialization/deserialization uses simple hex dumps for only 50% linkset data efficiency, I'm planning on checking out a modified base64 approach for 75% efficiency in case I can get good performance out of it. // Speeds up listlength checks at the cost of a bit of memory list EMPTY_LIST = []; // Decimal to hex, with a set number of digits string hex(integer value, integer digits) { integer i; string ret; do { --digits; i = (value>>(digits<<2))&0xf; if(i>9) i+=7; ret += llChar(i+0x30); } while(digits); return ret; } // Allocate a new qb_array of at least the desired length list qb_new(integer bytes) { list ret; integer i; integer length = llCeil(bytes/12.0); // rounded up to the nearest 12 bytes for(i = 0; i < length; ++i) { // memory usage is more predictable when built this way ret += <0, 0, 0, 0>; // ZERO_ROTATION is <0, 0, 0, 1> } return ret; } // Read a value from a qb_array // Returns 0-255 on success, -1 on error integer qb_read(list array, integer address) { if(address < 0 || address >= (array!=EMPTY_LIST)*12) // outside bounds return -1; // calculate address parts integer address_cell = address/12; // the list cell integer address_component = (address/3)&3; // the quaternion component integer address_shift = (2-(address%3))<<3; // bytes are stored MSB first quaternion cell = llList2Rot(array, address_cell); integer component; // get the component, order x-y-z-s if(address_component == 0) component = (integer)cell.x; else if(address_component == 1) component = (integer)cell.y; else if(address_component == 2) component = (integer)cell.z; else component = (integer)cell.s; return (component>>address_shift)&0xff; // get the byte } // Write a value 0-255 to a given address on a qb_array // Returns modified list on success, unmodified list on failure // Due to the piecewise nature of qb_arrays, to write we must first read the old quat // Using a global list instead of passing it as parameters/returns would allow // code reuse between reads and writes easily, making them into one function list qb_write(list array, integer address, integer value) { if(value & 0xffffff00) // invalid value return array; if(address < 0 || address >= (array!=EMPTY_LIST)*12) // outside bounds return array; // calculate address parts integer address_cell = address/12; // the list cell integer address_component = (address/3)&3; // the quaternion component integer address_shift = (2-(address%3))<<3; // bytes are stored MSB first quaternion cell = llList2Rot(array, address_cell); integer component; // get the component, order x-y-z-s if(address_component == 0) component = (integer)cell.x; else if(address_component == 1) component = (integer)cell.y; else if(address_component == 2) component = (integer)cell.z; else component = (integer)cell.s; // mask out the old value, insert new value component = (component&~(0xff<<address_shift))|(value<<address_shift); if(address_component == 0) cell.x = component; // insert into quaternion else if(address_component == 1) cell.y = component; else if(address_component == 2) cell.z = component; else cell.s = component; array = llListReplaceList(array, (list)cell, address_cell, address_cell); return array; } // Serialize a qb_array into linkset data as hex with the given name qb_serialize_hex(list array, string name) { integer i; quaternion cell; integer value; integer component; string save; integer array_length = array!=EMPTY_LIST; for(i = 0; i < array_length; ++i) { cell = llList2Rot(array, i); for(component = 0; component < 4; ++component) { if(component == 0) value = (integer)cell.x; else if(component == 1) value = (integer)cell.y; else if(component == 2) value = (integer)cell.z; else value = (integer)cell.s; save += hex(value, 6); } } llLinksetDataWrite(name, save); } // Deserialize a qb_array from linkset data list qb_deserialize_hex(string name) { integer i; list array; quaternion cell; integer value; integer component; string load = llLinksetDataRead(name); integer load_length = llStringLength(load); for(i = 0; i < load_length; i+=6) { // using typecast is faster than llOrd value = (integer)("0x"+llGetSubString(load, i, i+5)); component = (i/6)&3; // order x-y-z-s if(component == 0) cell.x = value; else if(component == 1) cell.y = value; else if(component == 2) cell.z = value; else cell.s = value; if(component == 3 || i >= load_length-6) { // write on component 3, or last entry array += cell; cell = <0, 0, 0, 0>; // avoids junk data at last entry, if incomplete } } return array; } default { state_entry() { integer array_length = 256; list array = qb_new(array_length); llOwnerSay("Allocated array, length = " +(string)((array!=EMPTY_LIST)*12)); integer i; // test some random writes and reads list read_addresses; for(i = 0; i < 10; ++i) { integer a = (integer)llFrand(array_length); read_addresses += a; // save addresses for deserialize test integer v = (integer)llFrand(256); array = qb_write(array, a, v); llOwnerSay("Random access test: write to " + (string)a + "<-" + (string)v + ", got back " + (string)qb_read(array, a)); } qb_serialize_hex(array, "array"); // serialize to linkset data llOwnerSay("Serialized old array"); array = qb_new(12); llOwnerSay("Allocated array, length = " +(string)((array!=EMPTY_LIST)*12)); array = qb_deserialize_hex("array"); // get old one back llOwnerSay("Deserialized old array"); // confirm everything is ok for(i = 0; i < (read_addresses!=EMPTY_LIST); ++i) { integer a = llList2Integer(read_addresses, i); llOwnerSay("Deserialize test: read from " + (string)a + ", got " + (string)qb_read(array, a)); } } }
  23. https://wiki.secondlife.com/wiki/Slerp makes it easy to go between two arbitrary rotations scaled by a time variable. rotation slerp(rotation a, rotation b, float t) { return llAxisAngle2Rot(llRot2Axis(b /= a), t*llRot2Angle(b))*a; } default { state_entry() { rotation start_rotation = llEuler2Rot(<paste start rotation from edit tool>*DEG_TO_RAD); rotation dest_rotation = llEuler2Rot(<paste end rotation from edit tool>*DEG_TO_RAD); float t; float max_t = 2.0; // your time here float time_step = 0.1; for(t = 0; t <= max_t; t += time_step) { llSetLinkPrimitiveParamsFast(LINK_THIS, [PRIM_ROTATION, slerp(start_rotation, dest_rotation, t/max_t)]); llSleep(time_step); } } } Alternatively, without slerp, just compute a rotational increment that gets you where you want to be in the given time, but that could end up drifting and requires more thinking about what axis you're wanting to rotate around, etc., instead of just pasting in the endpoint rotations. default { state_entry() { float t; float max_t = 2.0; float time_step = 0.1; rotation increment = llEuler2Rot(<0, 0, 90>*DEG_TO_RAD*(time_step/max_t)); rotation current_rotation; for(t = 0; t < max_t; t += time_step) { current_rotation = llGetRot(); llSetLinkPrimitiveParamsFast(LINK_THIS, [PRIM_ROTATION, current_rotation * increment]); llSleep(time_step); } } }
  24. This is an intended change that's been coming for a few weeks now, you can probably find the details in the server forum and the server release notes linked there.
  25. In my pursuit of effective ways of storing and randomly accessing 8-bit binary data, I realized that even if packing 4 bytes per integer is quick, simple and pretty speedy, it has absolutely awful overhead -- a single integer in a list costs 16 bytes, 12 bytes of overhead+4 bytes of payload, giving you a ratio of 4/16 = 25% -- and the speed starts to crumble rather non-linearly as the lists get longer, necessitating making several smaller lists, which in turn increases the overhead. After checking packing integers into roughly every datatype without using any super fancy tricks, i.e. no base96 encoding or whatever since I'm not concerned only for the memory use but also complexity and performance, I came to the following conclusions: 1) If you want pure speed, use integers, but the memory cost is huge from all the overhead. List performance may become an issue since long lists might be required to pack enough bytes. If you want ABSOLUTE speed, don't even pack the bytes and just store them as integers, but oh boy that's wasteful. 2) If you want pure memory, use strings; since the script memory representation is utf-16, a dead simple way of packing bytes is just llChar(0x100+bytevalue), and reverse for llOrd, for a ratio of 1/2 = 50%, much better than integers. The downside is that strings start slightly faster than lists, but their speed advantage seems to fall apart quickly; llOrd on a long string has a whole order of magnitude slower operation than a list seek, and modifying is no better. 3) If you want a balance of both, use rotations a.k.a. quaternions. The overhead per list entry stays the same as for integers but you can pack in 3 times more data per entry! A float can hold a 24-bit* integer i.e. 3 bytes without losing precision, which means a quaternion can hold 12 bytes of payload, while its list entry use is only 12+16 = 28 bytes, for a ratio of 12/28 = 43%, closer to strings than integers in efficiency, with somewhat slower performance than pure integers for reading (but integer reading is already so fast that it's probably not a bottleneck, and is still much faster than strings), but interestingly write performance is better due to the list lengths needed to store the same number of entries being lower. The logic needed for the component separation and byte packing is not too complicated, even if by far the most complex of the 3 considerations listed here. 4) Floats, vectors and keys are not useful. Keys end up using more memory than the equivalent string representation (I can't quite figure that one out why), floats are just worse integers, vectors are just worse quaternions. *) Actually 25 bits since the sign bit is separate, but making use of it for just packing bytes adds too much complexity to be worth it. Yes, you could also use custom functions to convert a 32-bit integer into a float with identical representation and back, but again... too much additional complexity for me. Of course for getting things to linkset data, there's a whole new set of considerations since you're limited to utf-8 strings. TL;DR: using quaternion lists to store byte arrays is not normal, but in LSL it is.
×
×
  • Create New...