mirror of
https://github.com/Sei-Lisa/LSL-PyOptimizer
synced 2024-11-21 06:15:56 -07:00
551 lines
22 KiB
Python
551 lines
22 KiB
Python
# (C) Copyright 2015-2024 Sei Lisa. All rights reserved.
|
|
#
|
|
# This file is part of LSL PyOptimizer.
|
|
#
|
|
# LSL PyOptimizer is free software: you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License as
|
|
# published by the Free Software Foundation, either version 3 of the
|
|
# License, or (at your option) any later version.
|
|
#
|
|
# LSL PyOptimizer is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with LSL PyOptimizer. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
# Optimize calls to LSL library functions and parameters where possible
|
|
# This is dependent on the LSL function library.
|
|
|
|
from lslopt import lslcommon
|
|
from lslopt.lslcommon import Key, Vector, Quaternion, nr
|
|
from lslopt import lslfuncs
|
|
from strutil import unicode
|
|
|
|
SensorFunctions = {'llSensor', 'llSensorRepeat'}
|
|
# not sure about llRemoteDataReply but let's fall on the safe side
|
|
NoKeyOptimizationFunctions = {'llMessageLinked', 'llRemoteDataReply'}
|
|
|
|
def OptimizeArgs(node, sym):
|
|
"""Transform function arguments to shorter equivalents where possible."""
|
|
assert node.nt == 'FNCALL'
|
|
params = node.ch
|
|
name = node.name
|
|
|
|
if 'Loc' in sym:
|
|
# This is a UDF. We can't do anything for these.
|
|
return
|
|
|
|
if name in SensorFunctions:
|
|
# The cutoff value is at a bit less than 3.1275 for some reason,
|
|
# but we use 3.14159.
|
|
if (params[4].nt == 'CONST' and params[4].t == 'float'
|
|
and params[4].value > 3.14159):
|
|
params[4].value = 4.0
|
|
|
|
types = sym['ParamTypes']
|
|
if name not in NoKeyOptimizationFunctions:
|
|
# Transform invalid/null keys to "" with the exceptions above,
|
|
# e.g. llGetOwnerKey(NULL_KEY) -> llGetOwnerKey("")
|
|
for i in range(len(types)):
|
|
if types[i] == 'key':
|
|
if params[i].nt == 'CONST':
|
|
if not lslfuncs.cond(Key(params[i].value)):
|
|
params[i].value = u""
|
|
params[i].type = 'string'
|
|
|
|
|
|
# llGetObjectDetails return types
|
|
# Last: 49 (OBJECT_TEXT_ALPHA).
|
|
objDetailsTypes = 'issvrvkkkiiififfffkiiiiiiffkiviiksiisiiiiiifssivvf'
|
|
|
|
# llGetParcelDetails return types
|
|
# Last: 13 (PARCEL_DETAILS_SCRIPT_DANGER)
|
|
parcelDetailsTypes = 'sskkikiiivviii'
|
|
parcelMediaQTypes = \
|
|
{ 4: 'k' # PARCEL_MEDIA_COMMAND_TEXTURE
|
|
, 5: 's' # PARCEL_MEDIA_COMMAND_URL
|
|
, 10: 's' # PARCEL_MEDIA_COMMAND_TYPE
|
|
, 11: 'ii' # PARCEL_MEDIA_COMMAND_SIZE
|
|
, 12: 's' # PARCEL_MEDIA_COMMAND_DESC
|
|
, 13: 'f' # PARCEL_MEDIA_COMMAND_LOOP
|
|
}
|
|
|
|
# llGetPrimitiveParams and llGetLinkPrimitiveParams return types
|
|
primParamsTypes = \
|
|
{ # 0 (unassigned)
|
|
1: 'i*' # 1=PRIM_TYPE_LEGACY
|
|
, 2: 'i' # 2=PRIM_MATERIAL
|
|
, 3: 'i' # 3=PRIM_PHYSICS
|
|
, 4: 'i' # 4=PRIM_TEMP_ON_REZ
|
|
, 5: 'i' # 5=PRIM_PHANTOM
|
|
, 6: 'v' # 6=PRIM_POSITION
|
|
, 7: 'v' # 7=PRIM_SIZE
|
|
, 8: 'r' # 8=PRIM_ROTATION
|
|
, 9: 'i*' # 9=PRIM_TYPE
|
|
# 10 to 16: unassigned
|
|
, 17: 'svvf' # 17=PRIM_TEXTURE
|
|
, 18: 'vf' # 18=PRIM_COLOR
|
|
, 19: 'ii' # 19=PRIM_BUMP_SHINY
|
|
, 20: 'i' # 20=PRIM_FULLBRIGHT
|
|
, 21: 'iiffffv' # 21=PRIM_FLEXIBLE
|
|
, 22: 'i' # 22=PRIM_TEXGEN
|
|
, 23: 'ivfff' # 23=PRIM_POINT_LIGHT
|
|
# 24 :unassigned
|
|
, 25: 'f' # 25=PRIM_GLOW
|
|
, 26: 'svf' # 26=PRIM_TEXT
|
|
, 27: 's' # 27=PRIM_NAME
|
|
, 28: 's' # 28=PRIM_DESC
|
|
, 29: 'r' # 29=PRIM_ROT_LOCAL
|
|
, 30: 'i' # 30=PRIM_PHYSICS_SHAPE_TYPE
|
|
# 31: unassigned
|
|
, 32: 'vff' # 32=PRIM_OMEGA
|
|
, 33: 'v' # 33=PRIM_POS_LOCAL
|
|
, 34: '' # 34=PRIM_LINK_TARGET
|
|
, 35: 'v' # 35=PRIM_SLICE
|
|
, 36: 'svvfvii' # 36=PRIM_SPECULAR
|
|
, 37: 'svvf' # 37=PRIM_NORMAL
|
|
, 38: 'ii' # 38=PRIM_ALPHA_MODE
|
|
, 39: 'i' # 39=PRIM_ALLOW_UNSIT
|
|
, 40: 'i' # 40=PRIM_SCRIPTED_SIT_ONLY
|
|
, 41: 'ivv' # 41=PRIM_SIT_TARGET
|
|
, 42: 'sfff' # 42=PRIM_PROJECTOR
|
|
, 43: 'i' # 43=PRIM_CLICK_ACTION
|
|
, 44: 'iffi' # 44=PRIM_REFLECTION_PROBE
|
|
# GLTF parameters admit an empty string in any of the places. We're not
|
|
# prepared to deal with that.
|
|
# , 45: 'svvf' # 45=PRIM_GLTF_NORMAL
|
|
# , 46: 'svvfv' # 46=PRIM_GLTF_EMISSIVE
|
|
# , 47: 'svvfff' # 47=PRIM_GLTF_METALLIC_ROUGHNESS
|
|
# , 48: 'svvfvfifi' # 48=PRIM_GLTF_BASE_COLOR
|
|
, 45: '*' # 45=PRIM_GLTF_NORMAL
|
|
, 46: '*' # 46=PRIM_GLTF_EMISSIVE
|
|
, 47: '*' # 47=PRIM_GLTF_METALLIC_ROUGHNESS
|
|
, 48: '*' # 48=PRIM_GLTF_BASE_COLOR
|
|
, 49: 's' # 49=PRIM_RENDER_MATERIAL
|
|
, 50: 'i' # 50=PRIM_SIT_FLAGS
|
|
}
|
|
|
|
# llGetPrimitiveParams parameters with arguments. F=face, L=link.
|
|
primParamsArgs = \
|
|
{ 17: 'F' # 17=PRIM_TEXTURE
|
|
, 18: 'F' # 18=PRIM_COLOR
|
|
, 19: 'F' # 19=PRIM_BUMP_SHINY
|
|
, 20: 'F' # 20=PRIM_FULLBRIGHT
|
|
, 22: 'F' # PRIM_TEXGEN
|
|
, 25: 'F' # PRIM_GLOW
|
|
, 34: 'L' # PRIM_LINK_TARGET
|
|
, 36: 'F' # PRIM_SPECULAR
|
|
, 37: 'F' # PRIM_NORMAL
|
|
, 38: 'F' # PRIM_ALPHA_MODE
|
|
, 45: 'F' # PRIM_GLTF_NORMAL
|
|
, 46: 'F' # PRIM_GLTF_EMISSIVE
|
|
, 47: 'F' # PRIM_GLTF_METALLIC_ROUGHNESS
|
|
, 48: 'F' # PRIM_GLTF_BASE_COLOR
|
|
, 49: 'F' # PRIM_RENDER_MATERIAL
|
|
}
|
|
|
|
# llGetPrimMediaParams and llGetLinkMedia return types
|
|
# Last: 14 (PRIM_MEDIA_PERMS_CONTROL)
|
|
primMediaTypes = 'iissiiiiiiiisii'
|
|
|
|
# llGetEnvironment return types (not to be confused with llGetEnv)
|
|
getEnvTypes = \
|
|
{ 15: 'fff'
|
|
, 0: 'v'
|
|
, 1: 'iii'
|
|
, 2: 'vfffvvvi'
|
|
, 4: 'fff'
|
|
, 5: 'f'
|
|
, 6: 'ff'
|
|
, 9: 'rffivvv'
|
|
, 13: 'f'
|
|
, 14: 'rfvivvv'
|
|
, 10: 'fff'
|
|
, 11: 'fff'
|
|
, 8: 'vvv'
|
|
, 100: 'f'
|
|
, 101: 'vff'
|
|
, 102: 'ff'
|
|
, 103: 'ii'
|
|
, 104: 'v'
|
|
, 105: 'ff'
|
|
, 106: 'vv'
|
|
, 200: 'iif'
|
|
}
|
|
|
|
# Types for llGetVisualDetails parameters (anything not in this list is
|
|
# returned as an empty string)
|
|
visualDetailsTypes = \
|
|
{ 33: 'f', 'height': 'f'
|
|
, 38: 'f', 'torso_length': 'f'
|
|
, 80: 'f', 'male': 'f'
|
|
, 198: 'f', 'heel_height': 'f'
|
|
, 503: 'f', 'platform_height': 'f'
|
|
, 616: 'f', 'shoe_height': 'f'
|
|
, 692: 'f', 'leg_length': 'f'
|
|
, 693: 'f', 'arm_length': 'f'
|
|
, 756: 'f', 'neck_length': 'f'
|
|
, 814: 'f', 'waist_height': 'f'
|
|
, 842: 'f', 'hip_length': 'f'
|
|
, 11001: 'f', 'hover': 'f'
|
|
}
|
|
|
|
# Compatibility: list extraction function / input type (by type's initial),
|
|
# e.g. 'si' means llList2String can extract an integer.
|
|
listCompat = {'ss', 'sk', 'si', 'sf', 'sv', 'sr', 'ks', 'kk',
|
|
'is', 'ii', 'if', 'fs', 'fi', 'ff', 'vv', 'rr'}
|
|
|
|
defaultListVals = {'llList2Integer':0, 'llList2Float':0.0,
|
|
'llList2String':u'',
|
|
# llList2Key is set programmatically in FuncOptSetup
|
|
#'llList2Key':Key(u''),
|
|
'llList2Vector':Vector((0.,0.,0.)),
|
|
'llList2Rot':Quaternion((0.,0.,0.,1.))}
|
|
|
|
# Auxiliary function for llDumpList2String optimization
|
|
def CastDL2S(self, node, index):
|
|
"""Cast a list element to string, wrapping it in a list if it's a vector or
|
|
rotation.
|
|
"""
|
|
elem = self.GetListNodeElement(node, index)
|
|
assert elem is not False
|
|
if type(elem) != nr:
|
|
elem = nr(nt='CONST', t=lslcommon.PythonType2LSL[type(elem)], SEF=True,
|
|
value=elem)
|
|
if elem.t in {'vector', 'rotation'}:
|
|
return self.Cast(self.Cast(elem, 'list'), 'string')
|
|
return self.Cast(elem, 'string')
|
|
|
|
# Quick hack to work around lack of cached per-node ContainsFunctions info
|
|
def FnFree(self, node):
|
|
if node.nt == 'FNCALL':
|
|
return False
|
|
if node.nt in {'CONST', 'IDENT', 'FLD'}:
|
|
return True
|
|
return all(FnFree(self, node.ch[i]) for i in range(len(node.ch)))
|
|
|
|
# The 'self' parameter here is the constant folding object.
|
|
def OptimizeFunc(self, parent, index):
|
|
"""Look for possible optimizations taking advantage of the specific LSL
|
|
library function semantics.
|
|
"""
|
|
node = parent[index]
|
|
assert node.nt == 'FNCALL'
|
|
name = node.name
|
|
child = node.ch
|
|
if self.optlistlength and name == 'llGetListLength':
|
|
# Convert llGetListLength(expr) to (expr != [])
|
|
node = nr(nt='CONST', t='list', value=[], SEF=True)
|
|
parent[index] = node = nr(nt='!=', t='integer',
|
|
ch=[child[0], node], SEF=child[0].SEF)
|
|
|
|
if name == 'llDumpList2String':
|
|
assert child[0].t == 'list'
|
|
if (child[1].nt == 'CONST'
|
|
and child[1].t in {'string', 'key'}
|
|
and child[1].value == u""
|
|
):
|
|
# Convert llDumpList2String(expr, "") to (string)(expr)
|
|
node.nt = 'CAST'
|
|
del child[1]
|
|
del node.name
|
|
return
|
|
|
|
list_len = self.GetListNodeLength(child[0])
|
|
if list_len is not False and list_len == 1 and child[1].SEF:
|
|
# A single-element list can always be transformed regardless of
|
|
# the presence of function calls with side effects
|
|
parent[index] = CastDL2S(self, child[0], 0)
|
|
return
|
|
|
|
if node.SEF:
|
|
# Attempt to convert the function call into a sum of strings when
|
|
# possible and productive.
|
|
|
|
if list_len is False:
|
|
# Can't identify the length, which means we can't optimize.
|
|
return
|
|
|
|
if list_len == 0:
|
|
# Empty list -> empty string, no matter the separator
|
|
# (remember we're SEF).
|
|
parent[index] = nr(nt='CONST', t='string', value=u'', SEF=True)
|
|
return
|
|
|
|
# Only optimize if the second param is a very simple expression,
|
|
# otherwise the sums can get large.
|
|
if child[1].nt in {'CONST', 'IDENT'}:
|
|
threshold = 10
|
|
else:
|
|
return
|
|
|
|
# Apply a threshold for optimizing as a sum.
|
|
if list_len > threshold:
|
|
return
|
|
|
|
elems = [self.GetListNodeElement(child[0], i)
|
|
for i in range(list_len)]
|
|
|
|
# Don't optimize if an element can't be extracted or is a list
|
|
if any(i is False or type(i) == nr and i.t == 'list'
|
|
for i in elems):
|
|
return
|
|
|
|
# We reorder list constructors as right-to-left sums. When an
|
|
# element contains function calls, it will generate a savepoint,
|
|
# but with that strategy, the maximum extra stack size at the time
|
|
# of each savepoint is 1.
|
|
# If the first element has a function call, we may end up causing
|
|
# more memory usage, because in a list constructor, the first
|
|
# element has no stack to save; however, if any elements past the
|
|
# third have function calls at the same time, the memory we add
|
|
# will be compensated by the memory we save, because the 3rd
|
|
# element has 2 elements in the stack, therefore reducing it to 1
|
|
# is a save; similarly, any elements past the 3rd containing
|
|
# function calls cause bigger and bigger saves.
|
|
|
|
# Since we're also eliminating the llDumpList2String function call,
|
|
# that may count for the extra stack element added. Therefore, we
|
|
# disable this condition and optimize unconditionally.
|
|
|
|
#if (child[0].nt in ('LIST', 'CONST') and list_len >= 3
|
|
# and type(elems[0]) == nr and not FnFree(self, elems[0])
|
|
# and all(type(i) != nr or FnFree(self, i) for i in elems[2:])
|
|
# ):
|
|
# return
|
|
|
|
# Optimize to a sum of strings, right-to-left to save stack.
|
|
i = list_len - 1
|
|
newnode = CastDL2S(self, child[0], i)
|
|
while i > 0:
|
|
i -= 1
|
|
newnode = nr(nt='+', t='string', SEF=True,
|
|
ch=[CastDL2S(self, child[0], i),
|
|
nr(nt='+', t='string', SEF=True,
|
|
ch=[self.Cast(child[1], 'string').copy(), newnode]
|
|
)
|
|
])
|
|
parent[index] = newnode
|
|
# Re-fold
|
|
self.FoldTree(parent, index)
|
|
return
|
|
|
|
if (name in {'llList2String', 'llList2Key', 'llList2Integer',
|
|
'llList2Float', 'llList2Vector', 'llList2Rot'}
|
|
and child[1].nt == 'CONST'
|
|
):
|
|
# 2nd arg to llList2XXXX must be integer
|
|
assert child[1].t == 'integer'
|
|
|
|
listarg = child[0]
|
|
idx = child[1].value
|
|
value = self.GetListNodeElement(listarg, idx)
|
|
tvalue = self.TypeFromNodeOrConst(value)
|
|
const = self.ConstFromNodeOrConst(value)
|
|
if const is not False and node.SEF:
|
|
# Managed to get a constant from a list, even if the
|
|
# list wasn't constant. Handle the type conversion.
|
|
if (node.t[0] + tvalue[0]) in listCompat:
|
|
const = lslfuncs.InternalTypecast(const,
|
|
lslcommon.LSLType2Python[node.t],
|
|
InList=True, f32=True)
|
|
else:
|
|
const = defaultListVals[name]
|
|
|
|
parent[index] = nr(nt='CONST', t=node.t, value=const, SEF=True)
|
|
return
|
|
|
|
if listarg.nt == 'FNCALL':
|
|
if listarg.name == 'llGetObjectDetails':
|
|
|
|
# make it the list argument of llGetObjectDetails
|
|
listarg = listarg.ch[1]
|
|
value = self.GetListNodeElement(listarg, idx)
|
|
tvalue = self.TypeFromNodeOrConst(value)
|
|
const = self.ConstFromNodeOrConst(value)
|
|
if type(const) == int and self.GetListNodeLength(listarg) == 1:
|
|
# Some of these can be handled with a typecast to string.
|
|
if name == 'llList2String':
|
|
# turn the node into a cast of arg 0 to string
|
|
node.nt = 'CAST'
|
|
del child[1]
|
|
del node.name
|
|
return
|
|
# The other ones that support cast to string then to
|
|
# the final type in some cases (depending on the
|
|
# list type, which we know) are key/int/float.
|
|
finaltype = objDetailsTypes[const:const+1]
|
|
if (name == 'llList2Key' # checked via listCompat
|
|
or (name == 'llList2Integer'
|
|
and finaltype in {'s', 'i'}) # won't work for floats
|
|
or (name == 'llList2Float'
|
|
and finaltype in {'s', 'i'}) # won't work for floats
|
|
) and (node.t[0] + finaltype) in listCompat:
|
|
# -> (key)((string)llGetObjectDetails...)
|
|
# or (integer)((string)llGetObjectDetails...)
|
|
node.nt = 'CAST'
|
|
del child[1]
|
|
del node.name
|
|
child[0] = self.Cast(child[0], 'string')
|
|
return
|
|
|
|
# Check for type incompatibility or index out of range
|
|
# and replace node with a constant if that's the case
|
|
if (value is False
|
|
or type(const) == int
|
|
and (node.t[0] + objDetailsTypes[const])
|
|
not in listCompat
|
|
) and node.SEF:
|
|
parent[index] = nr(nt='CONST', t=node.t,
|
|
value=defaultListVals[name], SEF=True)
|
|
|
|
elif listarg.name in {'llGetPrimitiveParams',
|
|
'llGetLinkPrimitiveParams'}:
|
|
# We're going to work with the primitive params list.
|
|
listarg = listarg.ch[
|
|
0 if listarg.name == 'llGetPrimitiveParams'
|
|
else 1]
|
|
length = self.GetListNodeLength(listarg)
|
|
if length is not False:
|
|
# Construct a list (string) of return types.
|
|
# A '*' in the list means the type can't be
|
|
# determined past this point (used with PRIM_TYPE).
|
|
i = 0
|
|
returntypes = ''
|
|
while i < length:
|
|
param = self.GetListNodeElement(listarg, i)
|
|
param = self.ConstFromNodeOrConst(param)
|
|
if (param is False
|
|
or type(param) != int
|
|
# Parameters with arguments have
|
|
# side effects (errors).
|
|
# We could check whether there's a face
|
|
# argument and the face is 0, which is
|
|
# guaranteed to exist, but it's not worth
|
|
# the effort.
|
|
or param in primParamsArgs
|
|
or param not in primParamsTypes
|
|
):
|
|
# Can't process this list.
|
|
returntypes = '!'
|
|
break
|
|
returntypes += primParamsTypes[param]
|
|
i += 1
|
|
if returntypes != '!':
|
|
if (len(returntypes) == 1
|
|
and returntypes != '*'
|
|
and idx in {0, -1}
|
|
):
|
|
if name == 'llList2String':
|
|
node.nt = 'CAST'
|
|
del child[1]
|
|
del node.name
|
|
return
|
|
if ((name == 'llList2Key'
|
|
or name == 'llList2Integer'
|
|
and returntypes in {'s', 'i'}
|
|
or name == 'llList2Float'
|
|
and returntypes in {'s', 'i'}
|
|
)
|
|
and (node.t[0] + returntypes)
|
|
in listCompat
|
|
):
|
|
node.nt = 'CAST'
|
|
del child[1]
|
|
del node.name
|
|
child[0] = nr(nt='CAST', t='string',
|
|
ch=[child[0]], SEF=child[0].SEF)
|
|
return
|
|
|
|
# The position of parameters past the first asterisk can't
|
|
# be determined, so we only consider parameters before it.
|
|
asteriskPos = returntypes.find('*')
|
|
if (asteriskPos == -1
|
|
or 0 <= idx < asteriskPos
|
|
or asteriskPos - len(returntypes) < idx < 0
|
|
):
|
|
# Check for type incompatibility or index
|
|
# out of range.
|
|
if idx < 0:
|
|
# s[-1:0] doesn't return the last char
|
|
# so we make it positive to ensure correctness
|
|
idx += len(returntypes)
|
|
if ((node.t[0] + returntypes[idx:idx+1])
|
|
not in listCompat
|
|
and node.SEF):
|
|
parent[index] = nr(nt='CONST', t=node.t,
|
|
value=defaultListVals[name], SEF=True)
|
|
return
|
|
|
|
del returntypes
|
|
|
|
elif listarg.name == 'llGetParcelDetails':
|
|
listarg = listarg.ch[1] # 0 is vector pos
|
|
# TODO: llList2XXX(llGetParcelDetails)
|
|
|
|
elif listarg.name == 'llParcelMediaQuery':
|
|
listarg = listarg.ch[0]
|
|
# TODO: llList2XXX(llParcelMediaQuery)
|
|
|
|
elif listarg.name in {'llGetPrimMediaParams', 'llGetLinkMedia'}:
|
|
listarg = listarg.ch[
|
|
1 if listarg.name == 'llGetPrimMediaParams'
|
|
else 2]
|
|
# TODO: llList2XXX(llGetPrimMediaXXX)
|
|
|
|
elif listarg.name == 'llGetEnvironment':
|
|
listarg = listarg.ch[1] # 0 is vector pos
|
|
# TODO: llList2XXX(llGetEnvironment)
|
|
|
|
elif listarg.name == 'llGetVisualParams':
|
|
listarg = listarg.ch[1] # 0 is id
|
|
# TODO: llList2XXX(llGetVisualParams)
|
|
|
|
del listarg, idx, value, tvalue, const
|
|
return
|
|
|
|
if name == 'llDialog':
|
|
if self.GetListNodeLength(child[2]) == 1:
|
|
button = self.ConstFromNodeOrConst(
|
|
self.GetListNodeElement(child[2], 0))
|
|
if type(button) == unicode and button == u'OK':
|
|
# remove the element, as 'OK' is the default button in SL
|
|
child[2] = nr(nt='CONST', t='list', value=[], SEF=True)
|
|
return
|
|
|
|
if (name == 'llDeleteSubList'
|
|
or name == 'llListReplaceList' and child[1].nt == 'CONST'
|
|
and not child[1].value
|
|
):
|
|
# llDeleteSubList(x, 0, -1) -> [] if x is SEF
|
|
# llListReplaceList(x, [], 0, -1) -> [] if x is SEF
|
|
if (child[0].SEF
|
|
and child[-2].nt == 'CONST' and child[-1].nt == 'CONST'
|
|
and child[-2].value == 0 and child[-1].value == -1
|
|
):
|
|
parent[index] = nr(nt='CONST', t='list', value=[], SEF=True)
|
|
return
|
|
|
|
if name == 'llFrand':
|
|
# We can set a range when the input is known to be positive or negative
|
|
argmin = self.getMin(child[0])
|
|
argmax = self.getMax(child[0])
|
|
if argmin is not None and argmin >= 0:
|
|
# Positive argument
|
|
node.min = 0.
|
|
if argmax is not None:
|
|
node.max = lslfuncs.F32(argmax * 0.9999999403953552)
|
|
elif argmax is not None and argmax <= 0:
|
|
# Negative argument
|
|
node.max = 0.
|
|
if argmin is not None:
|
|
node.min = lslfuncs.F32(argmin * 0.9999999403953552)
|
|
|
|
def FuncOptSetup():
|
|
# Patch the default values list for LSO
|
|
if lslcommon.LSO:
|
|
defaultListVals['llList2Key'] = Key(lslfuncs.NULL_KEY)
|
|
else:
|
|
defaultListVals['llList2Key'] = Key(u"")
|