Allow ll.LinksetDataRead to return nil when the key pair does not exist in the data store.
tracked
Anna Salyx
ll.LinksetDataRead("key") and ll.LinksetDataReadProtected("key", "pass") both will return an empty string if trying to access a stored value that doesn't exist. Which is not "nil".
Can we get it so that if a key pair does not exist in the LSD store that the return is actually a "nil" type instead? It would also return nil on a protected key with a bad "pass".
We have access to the nil data type with SLua now, and it'd be more logical for a non-existent key to return nil for a simple boolean test. IMO. Just an idea at least.
Added thought:
maybe have "llcompat" have the return still be a zero length ("") string for, well, compatibility sake.
Log In
xaka Chayoo
In a selfish way I would like llLinksetDataRead("key") and ll.LinksetDataReadProtected("key", "pass") , in LSL, to return an empty string if non existent and I can add a "string" to be returned if its just a place holder, because stuff that I have has been coded for that, in SLua however it makes sense and having "llcompat" return an empty string as referred.
H
Harold Linden
Rider Linden Any thoughts on this? There's certainly quite a lot of functions that return
""
, NULL_KEY
, ZERO_VECTOR, 0, 0.0, ZERO_ROTATION, and []
on errors.My concerns are:
1) These exception cases aren't called out terribly explicitly in the implementation where we deal with
LLScriptLibData
. We'd have to be quite careful about tagging places where we previously returned (for example) ZERO_VECTOR
due to a lack of data, and cases where ZERO_VECTOR
is an actual value that we meant to return.This might be a non-problem in practice, since we currently have no way to distinguish between error sentinel and data that just happens to match the sentinel _anyway_. We could potentially just put the replacement logic in a function wrapper like we do for
llFindNotecardTextSync()
and friends.2) This mainly helps us avoid cases where you'd have to check
if llSomething() ~= SENTINEL_VAL then
, rather than if llSomething() then
, but is the juice worth the squeeze?3) Can we even do anything sensible in cases where there are multiple error sentinels, like with
llDetectedTouchUV()
, which could return TOUCH_INVALID_TEXCOORD
, or ZERO_VECTOR
? _Should_ we?Rider Linden
Harold Linden: I'm chewing on this... my back of the napkin response is mixed.
For some of the items it makes sense, the
ll.Detected*
functions, for instance, if the event doesn't support the information. I thought we'd made the null uuid falsy... so I don't see as much call for it there.
For many others (especially the float and integer functions) it would be difficult to tell from just the value if this was a failure or a legitimate 0. We'd have to implement something in the wrapper that did the appropriate checks.
Or we would need to add a failure flag to LLScriptLibData to indicate failure and update the simulator's implementations to set that flag. That might be the better approach.
H
Harold Linden
Rider Linden: Mmm,
NULL_KEY
is not falsey (only 0.0
and false
can ever really be falsey due to VM reasons). It has to be checked with if some_key.istruthy
.Always returning
nil
in cases where a sentinel value would be returned isn't necessarily worse than LSL, but it would _necessitate_ branching where it might not otherwise be required.I'm somewhat torn on it, especially given the complexity of the API. I suppose we can punt on it and introduce it in a new stdlib version later if it's contentious. stdlib versioning is much less of a pain under the new scheme than it is under Mono (since it was basically never done), though I haven't used it much yet.
Rider Linden
Harold Linden That's what I was thinking of ".istruthy"
I'm good with a punt too.
Tapple Gao
Relatedly, these functions could logically return
nil
rather than NULL_KEY
:- ll.AvatarOnSitTarget
- ll.AvatarOnLinkSitTarget
- ll.DetectedKey
- ll.DetectedOwner
- ll.DetectedRezzer
- ll.GetInventoryCreator(also shouts an error)
- ll.GetInventoryKey
- ll.GetLandOwnerAt
- ll.GetLinkKey
- ll.GetObjectLinkKey
- ll.GetOwnerKey
- ll.GetPermissionsKey
- ll.Name2Key
- ll.RezObjectWithParams(also shouts an error)
Tapple Gao
And these functions could logically return
nil
rather than ZERO_VECTOR
:- ll.DetectedGrab
- ll.DetectedPos
- ll.DetectedTouchBinormal
- ll.DetectedTouchBinormal
- ll.DetectedTouchPos
- ll.DetectedTouchST
- ll.DetectedTouchUV
- ll.DetectedVel
- ll.GetAgentSize
- ll.GetCameraPos
- ll.GetColor(it returns <1,1,1> on not found)
- ll.GetTextureOffset
- ll.GetTextureScale
- ll.GroundContour
- ll.GroundNormal
- ll.GroundSlope
- ll.Linear2sRGB
- ll.WorldPosToHUD
- ll.sRGB2Linear
And these could logically return
nil
rather than ZERO_ROTATION
:- ll.DetectedRot
- ll.GetCameraRot
And these could logically return
nil
rather than 0.0
:- ll.GetAlpha
- ll.GetCameraAspect
- ll.GetCameraFOV
- ll.GetHealth
- ll.GetObjectMass
- ll.GetSimStats
- ll.GetTextureRot
- ll.Ground
- ll.ListStatistics
- ll.Water
And these could logically return
nil
rather than 0
:- ll.DetectedLinkNumber
- ll.DetectedTouchFace
- ll.DetectedType
- ll.GetAgentInfo
- ll.GetAttached
- ll.GetInventoryPermMask(also shouts error)
- ll.GetInventoryType(returns INVENTORY_NONE = -1)
- ll.GetLinkNumberOfSides
- ll.GetLinkSitFlags
- ll.GetListEntryType(deprecated)
- ll.GetObjectPermMask
- ll.GetObjectPrimCount
- ll.GetParcelFlags
- ll.GetParcelMaxPrims
- ll.GetParcelPrimCount
- ll.Ord(matchesstring.byte;utf8.codepointraises error)
And these could logically return
nil
rather than false
:- ll.AgentInExperience
- ll.DerezObject
- ll.DetectedGroup
- ll.GetScriptState(also shouts error)
- ll.GetStatus
- ll.IsFriend
- ll.Is.LinkGLTFMaterial
- ll.OverMyLand
- ll.SameGroup
- ll.ScriptDanger
And these could logically return
nil
rather than{}
:- ll.GetClosestNavPoint
And these arguments to
ll.GetLinkPrimitiveParams
(currently fills these in with 0 entries):- PRIM_TEXTURE
- PRIM_RENDER_MATERIAL
- PRIM_COLOR
- PRIM_BUMP_SHINY
- PRIM_FULLBRIGHT
- PRIM_TEXGEN
- PRIM_NORMAL
- PRIM_SPECULAR
- PRIM_ALPHA_MODE
- PRIM_LINK_TARGET(not sure what this should do)
- PRIM_GLTF_BASE_COLOR
- PRIM_GLTF_NORMAL
- PRIM_GLTF_METALLIC_ROUGHNESS
- PRIM_GLTF_EMISSIVE
Kristy Aurelia
There's also a variety of functions that return
ZERO_VECTOR
, that would make sense to be nil
.I don't know all of them, but it includes things like
llGetAgentSize
, llDetectedPos
, getting OBJECT_POS
from llGetObjectDetails
.llDetectedRot
and OBJECT_ROT
are similar for rotation.MarieOmani Resident
I would vote to keep things as they are with NAK and EOF. They mean something different than nil.
H
Harold Linden
updated the status to
tracked
Thanks for the suggestion! I'll bring this up at the next user group meeting to see if there's consensus.
H
Harold Linden
Interesting, I'll have to think about the API implications.
Are there any other obvious functions that return "" as a sentinel value when it'd be more sensible to return
nil
? Maybe the notecard stuff that returns NAK
should logically return nil
, or would that be weird given that it has other sentinels like EOF
?Nexii Malthus
Harold Linden Hmmm,
- llGetInventoryNamereturns empty string if not found this could returnnil
- llGetInventoryDesccan return empty string if item has no description but think that makes sense to have as an empty string because that's what it is. Same withllGetObjectDesc.
- llGetDisplayNamecan return an empty string in certain conditions, but also has other weird return values possible ("???"/"Loading..."). Honestly this is really messy and needs to be really fixed / unified. Just returningnilon: empty strings,"???"and"Loading..."
- llGetUsernamelikewise can return""if unable to resolve, should returnnilinstead
- llKey2Namereturns an empty string for non-existent avatars/prims, this is often used as a useful presence check in a lot of scripts, could returnnilfor same usage
- llGetRenderMaterialreturns... a name, uuid or null key? Returning either string name, native uuid ornilwould be fine here
- llGetParcelMusicURLcan return an empty string, but not familiar with the function, guess it could returnnilif not set or failing the permission?
- llGetNotecardLineSyncis a bit complex yeah since it can returnNAKorEOFwhich are not comparable tonil
Anna Salyx
Harold Linden I'm not sure about ll.GetNoteCardLineSync. The NAK is specifically for cases when the notecard data has fallen out of the regeion cache.
A dataserver function to get the number of notecard lines and to get a notecard line Asynchronously loads the notecard data into the region cache which can then be retrieve by the synchronous method thereafter. But if that notecard data expires or is pushed off the stack, then a NAK is returned to say "hey, that's not currently available, try the other way." So, I'm not sure "nil" if wouldn't fulfill the same purpose. NAK just might be a functional synonym of nil. Unlike EOF which says "no more data", NAK or nil both could say "data not currently available".
Now that all said.... My own personal preference would be to keep NAK in this case since it mentally/naturally goes hand in hand with EOF for notecard reading: Line not available / No more data
Oh! And a zero length line should always return a zero length line of course.
SuzannaLinn Resident
Harold Linden
ll.LinksetDataWrite()
and ll.LinksetDataWriteProtected()
could accept nil as value to delete a key.This way they would behave more alike to tables.
But "" should still delete a key, not set the key to "", to be compatible with LSL scripts sharing the linkset data in the same object.
------
I would stay with
NAK
and EOF
in ll.GetNoteCardLineSync()
.Depending on the point of view each of them could be the
nil
one (I would choose EOF
).It's more clear to leave it as it is.
------
ll.GetDisplayName
returning "Loading..." is a kind of feature, it can be used to retry later (I have used it).It could be better to have a different return, something like a NAK, but not
nil
.SuzannaLinn Resident
Harold Linden
A bit of philosophy...
I think that we should have clear where to stop the Lua-lization of LL functions before going on.
For instance:
ll.GetInventoryName()
and ll.GetInventoryDesc()
returning nil
looks very good.And what about
ll.GetInventoryType()
?It could return
nil
instead of INVENTORY_NONE
, but we would have to remember not to use INVENTORY_NONE
(it would work the same if the constant was undefined, but it can't be because llcompat needs it).Or, should
ll.GetAgentList()
return nil
instead of an empty table?I think that we must keep in mind that the scripters commenting in SLua canny, in Scriptting-lua discord and in SUG meetings are mostly advanced scripters.
There are many not so advanced scripters waiting for SLua in main grid to start learning it. And we are making a very long list of changes for them to learn. And all the changes are useful, but perhaps not useful enough.
Nexii Malthus
SuzannaLinn Resident
re:
ll.GetDisplayName
that's all well and good until someone decides to set their display name to "Loading..." (I've had someone on my friends list with that for years) -- plus what about "..." and ""?
The function should return a single clear
nil
sentinel value, not several variations.Plus more conventional retry later is via
ll.RequestDisplayName
.Although I admit have had to implement a retry later while loop with
llGetDisplayName
to avoid a complex async code workaround on a critical script important to a region.Re: philosophy
Yeah, if there are already is a clear enumeration/constant like
NAK
, EOF
, INVENTORY_NONE
then there is no reason to return nil
instead.Also agree with functions that return a list to return an empty table. These can be easily checked with the native # count. E.g.
local agents = ll.GetAgentList(...); local agentCount = #agents;
Tapple Gao
Harold Linden more:
- ll.HttpHeaderreturns "" for a header that doesn't exist
- ll.GetLinkNamereturns NULL_KEY
- llGetRenderMaterialreturns NULL_KEY
- llGetTexturereturns NULL_KEY
- ll.JsonGetValuereturns JSON_INVALID
- ll.JsonValueTypereturns JSON_INVALID
These ones do, but also shout an error, so may not count:
- ll.GetAnimationOverride
- ll.HMAC
- ll.GetInventoryAquireTime
- ll.GetInventoryDesc
- ll.SignRSA
- ll.XorBase64
SungAli Resident
SuzannaLinn Resident In the case of functions like ll.GetAgentList, I vote for an empty table. This is because an empty table is the correct answer to the request, whereas a nil value generally represents an exception/error. Returning a nil value would require our scripts to respond to that value with some sort of exception processing, whereas looping through an empty list simply becomes an effective no-op, which is what the scripter will want much of the time. When an empty list is not processable as a no-op (i.e, the script needs at least one agent to be present to do whatever), then checking for a table length of 0 to generate the exceptional response is not significantly more work than checking for a nil response.
The case for reading linkset data is more complex. It seems to me that returning a nil value is only useful if we could assign "" as a valid value, but this raises compatibility issues with LSL scripts accessing the same data (Is this even an issue? Why would we be mixing LSL and SLua in the same object? Yes. Legacy objects that we're enhancing. Using no mod scripts from other sources . ) Looping through a list of existing keys, you'll never get the sentinel value. In tables, returning nil for non-existent keys and removing keys by setting their values to nil lets us treat nil like any other value when we so choose. But in linkset data, the stored value is always a string and setting the key to "" deletes the entry. So long as this behavior exists, the logical, most table-like, behavior would be to return "" for the same reason. Example, I'm using the UUIDs of the players of my game as keys, and keeping a list of achievements that they have accomplished. The list of possible achievements might be large so to save space, I only record those actually accomplished. So when I need to know if the player has achieved goal_X, I read the string associated with their key and search it for the goal_X tag. In this case "", as a return for a player who hasn't accomplished anything yet, is much more useful to me than a nil return since I don't have to make exceptions in my code for players with no accomplishment.
Bottom line: if the sentinel value can be interpreted as a valid response, then return "", 0, false, whatever, and reserve nil for cases where the sentinel value always (or at least nearly always) flags an exception that must be handled as such.
Anna Salyx
SungAli Resident Not addressing beyond the scope of what I had originally ask for here, the motivation for my request was simple: easy detection of a "non-existent key". For example
local LSDValue = ll.LinksetDataRead("no key")
LSDValue as it currently sits will be: ""
when I then test:
if LSDValue then
do something with the return
else
do something for a key that doesn't yet exist.
end
if the return was a "nil" we'd have a simple block here. But "" always return true in an if statement, so we have to extra steps to see if the return was "" or actually something we wanted. I mean, it's sot horrible onerous to be sure, but it's also less than elegant.
That was my only driver for that one and I still say it'd be useful for some functions that return "" to return nil instead just for simplicity in testing for. Returning "false" or "0" wouldn't really work here since "false" would be stored as a string value and not the boolean. and "0" might be a perfectly key-value pairing. Only nil allows for a quick, simple, and intuitive "if test". that I can see. I could be wrong. But that was my line of thought on the subject when opening this.