Skip to content

Rulesets for table-based function params#143

Merged
Rider-Linden merged 12 commits into
mainfrom
rider/fluent
Jun 30, 2026
Merged

Rulesets for table-based function params#143
Rider-Linden merged 12 commits into
mainfrom
rider/fluent

Conversation

@Rider-Linden

@Rider-Linden Rider-Linden commented Jun 4, 2026

Copy link
Copy Markdown
Collaborator

Generates a lua interface for functions requiring structured lists of parameters with an initial implementation for llprim.particleSystem, llprim.setMedia, and llhttp.request

I selected each of those three because each had a subtly different catch.

related PRs:

secondlife/slua#86
https://github.com/secondlife/server/pull/2574

llprim.particleSystem({
    pattern       = PSYS_SRC_PATTERN_ANGLE_CONE,
    texture       = "da8e96f5-4ada-4f37-bd2c-cf3e68c49a42",
    color_begin   = vector(1, 0.6, 0.1),
    color_end     = vector(1, 0.1, 0),
    alpha_begin   = 1.0,
    alpha_end     = 0.0,
    scale_begin   = vector(0.1, 0.1, 0),
    scale_end     = vector(0.4, 0.4, 0),
    glow_begin    = 0.5,
    glow_end      = 0.0,
    accel         = vector(0, 0, 0.5),
    burst_speed_min = 0.5,
    burst_speed_max = 1.5,
    burst_rate    = 0.05,
    burst_count   = 5,
    burst_radius  = 0.1,
    src_max_age   = 0,     -- 0 = continuous
    part_max_age  = 3.0,
    color_interp  = true,
    scale_interp  = true,
    emissive      = true,
    wind          = false,
}, link)

-- note link is an optional parameter.  If missing it defaults to LINK_THIS

setMedia ends up looking like this:

local link = nil

llprim.setMedia(0, {
    current_url          = "https://example.com/",
    home_url             = "https://example.com/",
    auto_play            = true,
    auto_loop            = false,
    controls             = PRIM_MEDIA_CONTROLS_STANDARD,
    width_pixels         = 1024,
    height_pixels        = 768,
    whitelist_enable     = true,
    whitelist            = { "example.com", "*.example.com" },
    perms_interact       = PRIM_MEDIA_PERM_ANYONE,
    perms_control        = PRIM_MEDIA_PERM_OWNER,
}, link)

-- Note, the first parameter is the face.  The last parameter is the link number.  If link number is omitted 
-- it defaults to LINK_THIS

http request:

local request_id = llhttp.request(
    "https://api.example.com/data",
    {
        method          = "POST",
        mimetype        = "application/json",
        verify_cert     = true,
        extended_error  = true,
        custom_header   = {
            ["X-Api-Key"]        = "my-secret-key",
            ["X-Request-Source"] = "second-life",
        },
        accept          = { "application/json", "text/plain" },
    },
    '{"hello": "world"}'
)

Comment thread lsl_definitions.yaml
tooltip: How long this particle system should last, 0.0 means forever.
type: integer
value: 19
value-type: float

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes. this is where I was hoping you'd put this data, rather than in the ruleset section

Comment thread lsl_definitions/slua.py
Comment on lines +21 to +28
_TABLE_RULESET_TYPE_MAP: dict[str, str] = {
"float": "number",
"integer": "number",
"vector": "vector",
"string": "string",
"key": "(string | uuid)",
"asset": "(string | uuid)",
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll formalize this better in a future PR. I do like this structure over all the loose -semantics fields laying around

Comment thread lsl_definitions/generators/lscript.py Outdated
Comment on lines +914 to +915
apply_fn = ruleset_data["apply-fn"]
apply_link_fn = ruleset_data["apply-link-fn"]

@tapple tapple Jun 4, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will definitely need improvement at some point (now, later, whatever)

apply_fn has the right signature for

  • llCreateCharacter
  • llParcelMediaCommandList
  • llSetCameraParams
  • llSetGroundTexture
  • llUpdateCharacter

apply_link_fn doesn't have the right signature for any function except for llLinkParticleSystem, but it's close for these (it's missing face):

  • llSetLinkGLTFOverrides
  • llSetLinkMedia
  • llSetPrimMediaParams

neither one has the right signature for

  • llRezObjectWithParams
  • llSetAgentEnvironment
  • llSetEnvironment
  • llCastRay
  • llEvade (no params yet)
  • llFindNotecardTextCount (no params yet)
  • llFindNotecardTextSync (no params yet)
  • llFleeFrom (no params yet)
  • llGetAgentList (no params yet)
  • llGetAttachListFiltered
  • llGetClosestNavPoint
  • llGetStaticPath
  • llGiveAgentInventory
  • llHTTPRequest
  • llMapBeacon
  • llNavigateTo
  • llPatrolPoints
  • llPursue
  • llSetKeyframeMotion
  • llSetParcelForSale
  • llTransferOwnership (no params yet)
  • llWanderWithin

tapple

This comment was marked as duplicate.

Comment thread generated/secondlife_selene.yml Outdated
@@ -0,0 +1,47 @@
// particle-params
static const FluentParamDescriptor kParticleParamsDescs[] = {
{"flags", 'i', 0},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if both flags and booleans are given, they are ored together?

Comment thread lsl_definitions.yaml Outdated
Comment thread slua_definitions.yaml Outdated
type: '{[string]: any}?'
return-type: ParticleParams
methods:
- name: apply

@tapple tapple Jun 4, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

from Harold:
Is the builder itself necessary there? What utility does it provide over an exported declared type for the dict-like table and a function that accepts tables of that shape? That's generally how similar APIs work in other Luau projects, the "builder-ness" of SPP isn't something that's really needed elsewhere.
That way serialization is easier as well, since you don't need to preserve the type

I don't know if a fluent wrapper is really what we want / need here. Just declaring an expected table shape and a function that consumes it should be fine, since that's really all the builder does

@tapple tapple Jun 4, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah. that's true. most lua api's would be like

llprim.particleSystem{
    emissive = true,
    target_linear = true,
    pattern = PSYS_SRC_PATTERN_EXPLODE,
    start_color = vector(0, 0, 1),
    src_max_age = 10,
    burst_rate = 0.1,
    burst_count = 10,
    target_key = ll.GetOwner()
}

"just a function" would work better for most of the param-dict wrappers: #143 (comment)

@tapple tapple Jun 4, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm unsure whether those functions should look like 1.

function llprim.setMedia(link: number, face: number, params: {})

to match the lsl function, or like 2.

function llprim.setMedia(params: {}, link: number?, face: number?)

so the link/face can be optional (defaulting to LINK_THIS and ALL_SIDES, or
3. mix the two and make a mess like table.insert does:

llprim.setMedia: ((link: number, face: number, params: {}) -> ())
    & ((face: number, params: {}) -> ())
    & ((params: {}) -> ())

or, 4. just add link number/face number as optional dict params, and leave positional params out of it entirely

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh. the dict parameter should be optional too, and work the same as an empty list. Most of the param-dict functions make sense with an empty param list

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nil and an empty table should both behave like an empty list.

Comment thread lsl_definitions/generators/lscript.py Outdated
body = "\n".join(desc_lines)
section = (
f"// {ruleset_name}\n"
f"static const FluentParamDescriptor {array_name}[] = {{\n{body}\n}};\n"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it still true that the bulk of the code to generate the builder lives in lsl-definitions, and slua/tools/gen_primparams_methods.py calls that code and then converts it to c struct format? just thinking ahead to what changing the structure of rulesets in the yaml file would actually look like given that it's growing in capability

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

from Rider:
I believe that yes, gen_primparams_methods is called out of the slua repo.
I'm not entirely happy with that design since it means that slua needs to match the simulator.

That implies that ANY change to lsl_definitions requires a recompile on the VM as well.
(spelling correction to a tooltip? new VM)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I got the idea somewhere in my mind that there was an intermediate json file that gen_all_definitions wrote and that gen_primparams_methods read, but, I can't find anything like that in reality
would probably be better if that was reality, so that slua only needed the lsl-definitions build, not lsl-definitions itself
but, I can understand why it isn't that way right now. PrimParams isn't even in beta yet, unless I missed it

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

from Rider:
It has to do with autobuild and version numbers. build lsl_definitions and the version goes up. Update the simulator and the version no longer matches the one use to build the VM and autobuild screams about version mismatches.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

from Harold:
prior feedback RE the codegen for those wrappers being in slua proper so particles can be mocked / re-implemented for local dev / testing as with the SPP wrapper. There's not really a better place to put them other than slua that keeps the local dev story sane. Ideally people should only have to re-implement the very low-level implementation details of the thing that our wrappers wrap (ll.ParticleSystem here, since that's the thing that does the work) so things like do local simulation a la LSLForge can work correctly without a ton of work on their part.

Comment thread lsl_definitions.yaml
value: '0x400'
PSYS_PART_START_ALPHA:
member-of: [ParticleParam]
pretty-name: alpha_begin

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice. I like pretty-name better than method-name used in rulesets

Comment thread generated/secondlife.d.luau Outdated
@Rider-Linden Rider-Linden marked this pull request as ready for review June 23, 2026 17:08
@Rider-Linden Rider-Linden requested a review from Copilot June 23, 2026 18:02

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds “table rulesets” to generate typed SLua/Luau table-param interfaces (and corresponding fluent serialization descriptors) for structured list-based LSL APIs, with initial coverage for particles, prim media, and HTTP requests.

Changes:

  • Introduces builder-rulesets of type: table plus value-type / pretty-name metadata to derive stable, typed table keys from enum-member constants.
  • Extends SLua/Luau generation to emit ParticleParams, MediaParams, and HttpRequestParams types and new SLua module/functions (llhttp.request, llprim.particleSystem, llprim.setMedia).
  • Adds a new generator for C++ fluent descriptor arrays + dispatch wrappers, and updates Mono C# generation to escape reserved keywords.

Reviewed changes

Copilot reviewed 8 out of 19 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
slua_definitions.yaml Adds exported table-param type aliases and new SLua module/function signatures.
lsl_definitions/slua.py Adds table-ruleset type mapping and generation of typed table aliases/properties from rulesets.
lsl_definitions/rulesets.py Generalizes ruleset expansion and adds table-ruleset expansion helpers + semantic mapping.
lsl_definitions/lsl.py Extends constant model with value_type / pretty_name and adjusts ruleset validation for table rulesets.
lsl_definitions/generators/lscript.py Adds C# keyword escaping and generates fluent builder descriptor C++ fragments for table rulesets.
lsl_definitions.yaml Updates enums/constants with value-type/pretty-name and declares the new table rulesets (http/particles/media).
lsl_definitions.schema.json Extends schema to support both builder and table rulesets and new constant metadata fields.
generated/templated/LslLibrary.cs Regenerates Mono bindings with escaped identifiers (e.g., @params).
generated/syntax/slua.tmLanguage.json Regenerates syntax highlighting to include new modules/functions/types.
generated/syntax/slua.tmLanguage Regenerates syntax highlighting to include new modules/functions/types.
generated/secondlife.docs.json Regenerates docs to include new llhttp and new llprim functions and updated constant docs.
generated/secondlife.d.luau Regenerates Luau definitions with the new exported param-table types and function signatures.
generated/secondlife_selene.yml Regenerates Selene std definitions with new functions and updated constant descriptions.
generated/lua_keywords_pretty.xml Regenerates viewer keyword/tooltip output with new types/functions and updated tooltips.
generated/lsl_keywords_pretty.xml Regenerates viewer keyword/tooltip output with updated constant tooltips.
generated/experimental/enums.txt Regenerates experimental enum listing (incl. ParticleSourcePattern as enum).
generated/cpp/fluent_builder_descriptors.cpp Adds generated fluent param/flag descriptors and wrapper registrations for the new table rulesets.
gen_all_definitions.sh Updates generator invocation to prefer python3 when available.
.gitignore Ignores *.code-workspace files.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread lsl_definitions/generators/lscript.py Outdated
Comment thread lsl_definitions.schema.json
Comment thread slua_definitions.yaml Outdated
Comment thread slua_definitions.yaml Outdated
Comment thread slua_definitions.yaml Outdated
Comment thread lsl_definitions/slua.py
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
type LLJsonDecodeOptions = LLJsonDecodeOptionsWithoutPath | LLJsonDecodeOptionsWithPath
export type ParticleParams = {
flags: number?,
color_begin: vector?,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reckon it's possible to document the defaults some of these values have?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Suzanna-Linn Has a PR with an RFC proposing that: #160

@monty-linden monty-linden left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

zoooom

@Rider-Linden Rider-Linden merged commit 2879903 into main Jun 30, 2026
4 of 5 checks passed
@Rider-Linden Rider-Linden deleted the rider/fluent branch June 30, 2026 15:55
@github-actions github-actions Bot locked and limited conversation to collaborators Jun 30, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants