This is an attempt to create a set of open source LSL scripts for the operation of elevator systems in Second Life.
Currently, the Delta Elevator is the most sophisticated elevator system in Second Life - by far. However, its scripts are closed source. This means using the Delta will introduce no-mod scripts to a build, which can be an issue.
The Falcon will focus on core features. It does not aim to copy the full feature-set of the Delta. Also, with the target audience being creators, there won't be much effort on making the setup process particularly user-friendly.
The goal is to have a solid and versatile script that enables creators to come up with custom tailored elevator systems for their own builds or for distribution to end-users.
This is a very early work-in-progress. There isn't much to see here yet.
This project is making use of Firestorm's
LSL preprocessor, more
specifically its #include
and #define
features. You'll need those in order
to compile the scripts. To do so, point the viewer's script include directory
to the lib
folder of this repo.
The following are notes on how the system is intended to work; these are my personal implementation notes/concepts. Unless you are working on an elevator script yourself and want some inspiration, this won't be of use to you.
Identifiers represent the relevant concepts of an elevator system:
bank
, a set of elevators that work in conjunctionshaft
, conceptually equivalent to an elevator cabfloor
, a landing that a cab can travel to
The following components are required for operation:
name | number | associated with |
---|---|---|
controller |
1 per system | bank |
cab |
1-4 per bank |
bank |
doorway |
1 per shaft & floor |
bank , shaft , floor |
call_buttons |
0-4 per floor |
bank , floor |
Note that the call_buttons
may be implemented as subcomponent of a
doorway
instead, hence why they are listed as optional above.
The doorway
could, in the simplest case, be an invisible prim or piece
of the floor in front of the cab entrance. Its main purpose is to
determine the position of the floor
landings, i.e. where the cab
s
are supposed to travel to exactly when they are being called to a
specific floor
.
The following components are optional and should come as a linked prim of a specific main component:
name | number | parent component |
---|---|---|
cab_controls |
0-1 per cab |
cab |
cab_display |
0-n per cab |
cab |
cab_doors |
0-n per cab |
cab |
doorway_display |
0-n per doorway |
doorway |
doorway_doors |
0-n per doorway |
doorway |
call_buttons |
0-1 per doorway |
doorway |
The handling of subcomponents is not set in stone. For some systems, it can make more sense to have individual scripts take care of these subcomponents. In other systems, it might be sufficient to have a single script in the parent component take care of the subcomponents.
I'm hoping to be able to eventually get two reference implementations out there; one for each of these cases. For now, one will have to do.
- All communcation between the main components always goes through the controller; this means that even a system with only one cab needs to have a controller
- The controller only knows the main components, it does not care about the subcomponents (separation of concerns)
- Call buttons can be main components or subcomponents of doorways, because we want the option to have a single call button for a set (bank) of elevators, but we also want the option to simplify the system by letting the doorways deal with the buttons
- Parse description
On rez, all components read their description and parsebank
name,shaft
name andfloor
name, if applicable - Setup listener
Thecontroller
and all components of typecab
,doorway
andcall_buttons
set up a listener on the same channel - Request pairing
On touch, thecontroller
broadcasts apair
message on said channel; the message contains at least thebank
name - Confirm pairing
All listening components check if thebank
name matches their own; if so, they message thecontroller
back with astatus
message, specifying theirshaft
andfloor
name, if applicable - Remember components
Thecontroller
creates and maintains lists of all paired components - Sanity check optional
Thecontroller
checks if all required components are in place; that is, at least onecab
and at least twodoorway
objects for eachcab
;call_buttons
are seen as optional as they might be implemented as a subcomponent of thedoorway
objects - Find base doorway
Thecontroller
queries the location and rotation of the basedoorway
(the one closests in z-position to the associatedcab
); this will also determine the recall floor for theshaft
of thatdoorway
, as well as the z-offset betweencab
anddoorway
s
- Request doorway config
Thecontroller
sends a message to alldoorway
s, instructing them to perform configuration; this message includes the x- and y-position and rotation as queried from the base doorway, as well as their custom floor number/name, if any; additionally, it includes a list of allfloor
s and their accessibility (this is important so that thedoorway
s can configure linkedcall_buttons
correctly, if any) - Peform doorway config
Alldoorway
s undergo configuration, then send astatus
message back to thecontroller
- Request cab config
Thecontroller
sends a message to allcab
s, instructing them to perform configuration; the message includes a list of allfloor
s, including their custom floor names and accessibility by thatcab
- Perform cab config
Allcab
s perform configuration, including setting up their subcomponents; thecab
s then send astatus
message back to thecontroller
- Request button config
Thecontroller
sends a message to allcall_buttons
(if any), instructing them to perform configuration; the message contains a list of allfloor
s, including their custom names - Perform button config
Allcall_buttons
, if any, perform configuration and report back to thecontroller
with astatus
message - Inform about success
If all components were configured successfully, thecontroller
sends out messages to all registered components, informing them that the system is ready - Finish up optional
All components may now peform additional steps to get into their individual idle/ready states; for example, allcab
s anddoorway
s might want to close their doors, if any, etc
This depends on how the cab is constructed and implemented. There are generally three different approaches here:
- The
cab
s subcomponents are separate (but linked) prims with separate scripts controlling them - in this case, setup could look similar to how the controller is setting up the main components (see above), meaning that thecab
sends out a message to all prims in the linkset, keeps a list of all relevant prims that replied, then instructs them to perform setup; - The
cab
s subcomponents are separate (but linked) prims with no additional scripts - in this case, thecab
would do all the work of scanning the linkset, identifying the relevant objects and setting them up as needed; - The
cab
s subcomponents could be implemented as touchable texture areas or in similar ways that do not require additional prims; thecab
would then do all the work that the subcomponents might otherwise handle
The same general logic as for the cab subcomponent initialization applies, see above.
Each main component needs to have certain information present in its description field, which will be used for initialization. This is different from the Delta, which will use more advanced logic instead. However, using a description string is a very solid approach that allows for relatively simple script logic and isn't too hard to set up either.
The general syntax of the ident-string
is as follows:
bank:shaft:floor
Depending on the component, only some of that information might be required:
component | description syntax | example |
---|---|---|
controller |
bank |
south-bank |
cab |
bank:shaft |
south-bank:shaft1 |
doorway |
bank:shaft:floor |
north-bank:shaft-left:2 |
call_buttons |
bank::floor |
north-bank::3 |
Note that call_buttons
have an empty string for the shaft
name,
hence the two colons (::
).
Since :
is used as delimiter, it can't be used as part of any of the
identifiers. In fact, identifier names may only contain alphanumeric
chacters (a-z, 0-9), underscores and dashes.
Messages can contain a maximum of 1024 ASCII characters. The keywords involved should therefore be kept rather short in order to maximize the bytes available for the payload.
The general message syntax is as follows:
signature ident-string command parameters
Example:
falcon-cab bank1:shaft2 status ready
The signature identifies the type of component that sent the message:
component | signature |
---|---|
controller |
falcon-control |
cab |
falcon-cab |
doorway |
falcon-doorway |
call_buttons |
falcon-buttons |
The command is a string that tells the type and meaning of a message. The list of commands is subject to frequent change for the time being.
Note to self: commands should be as system-agnostic as possible. For
example, an elevator system might not have buttons in the cab, but a
simple lever for "up" and "down". Also, there might be no doors on the
cab and/or doorways at all. Hence, a command like doors_closed
seems
too specific, makes too many assumptions. Instead, something like
ready_for_departure
should work for pretty much any system.
The parameters can be empty or any arbitrary string. The meaning and how to parse the parameter string depends on the type of message. For every command, the number and meaning of its parameters shall be documented.
In the following, a >
indicates a message from the controller to the
components, <
is a message from a component to the controller.
> ping
Broadcast message to scan the sim for components. Matching components
(those with the same bank
name) should reply to the sender via pong
.
< pong
A component's reply to a controller's ping
message, given that their
bank
names are a match.
> reset
Instruct the component to perform a complete script reset.
> channel [channel]
Instruct a component to switch to the given channel for all communication from this point forward.
> pair [force] [channel]
Request a component to pair up with the controller if the bank
name matches.
Components are expected to reply with a status
message. If the force
parameter is given and 1
, components should pair to the controller even if
they were already paired to another controller. If the channel
parameter is
given, components should additionally change to the specified channel.
> status
< status component-status controller-uuid [status-params...]
When send by the controller, this is a request for a status
reply by
all applicable components; that is, all components with the same bank
.
If send by a component, this is a reply to a status
or pair
request.
Informs about the general state of a component, which also indicates whether the
component is paired to a controller or not. If paired, the controller UUID is
provided, otherwise it will be the NULL key.
component-status
can be either of the following (subject to change):
default
: initial state; only basic initialization has been doneconfig
: currently undergoing the configuration processrunning
: configuration complete, component is operationalerror
: not operational due to some error
> config [config-params...]
Instruct components to undergo configuration. Depending on the component, the
config-params
can differ, but will typically include information about the
existing floors, so that button panels in the cab as well as on the floors can
be set up accordingly.
< event event-name event-type [event-details...]
Informs the controller about an event. An event could be when an avatar has clicked on one of the floor buttons, when an elevator cab has arrived at its destination, when a doorway's doors have finished opening/ closing, etc.
The event-type
informs about whether the event has just started, ended or is
an event without duration (in which case event-type
would be none
).
(SUBJECT TO CHANGE) Possible events might include:
call [type] floor [dir]
: A call button has been pressed for the given floormove [type] from to
: Cab has startet/finished moving fromfrom
toto
open [type]
: Cab/doorway has started/finished opening its doorsclose [type]
: Cab/doorway has started/finished closing its doors???
: User clicked the 'open doors' button???
: User clicked the 'clsoe doors' button???
: User clicked some other special button
> action [action-type] [action-parameters]
(SUBJECT TO CHANGE) Instructs a component to perform a certain action-type
:
move z-pos [speed]
: Request a cab to travel to the given z-positionhalt
: Request a cab to halt entirely (simulating power outage, for example)open
: Request a cab/ doorway to open its doors (or equivalent)close
: Request a cab/ doorway to close its doors (or equivalent)mode [mode]
: Request a component to switch to a specific mode of operation
Most importantly, I'm undecided if we should have 'smart' or 'dumb' components:
In the first case (smart components), the controller would simply forward most
events that it learns about to the other components. Those could then decide
upon whether and how to react to such an event. Example: a cab
arriving on
a floor sends an event
to the controller, the controller forwards it to all
other components. A call_button
on that floor could then decide to stop
indicating. Another example would be a cab opening its doors automatically on
arrival on a floor, instead of waiting for a seperate instruction to do so.
This could lead to "badly behaving" components if components don't react to
certain events correctly - for example, if the system is emergency mode because
of a fire, maybe cabs should keep their doors closed on affected floors.
Essentially, this means that components need to have more knowledge of the state
of the system; this "shared" state would need to be kept consistent across the
controller and all components.
In the second case (dumb components), components will not do anything unless specifically instructed to do so by the controller. This means a lot more work, but also more fine-grained control for the controller. It also means a very clear separation of concerns and that the system's state would be held only in the controller. Components would not make any assumptions at all and only do precisely what they are asked to do. Performance-wise, it would keep the multiple comoponent scripts smaller, while the single controller script would be bigger. On the other hand, much more communication between components and controller will be needed, which could introduce lag.
The decision is important for the design of the event
and action
commands.
For now, I think I'd like to try and implement the dumb approach, as it keeps the state and responsibility for the system entirely within the controller. Should the complexity of this approach become overwhelming, I can scrap all progress on it, cry a little, then start working on the smart approach.
There are multiple scenarios we need to think through that will probably require some scripting to make sure all is handled well. We'll collect these things here for now:
- Can the cabs be linked to the rest of the build?
- Would the scripting be the same for linked or unlinked cabs?
- What happens when something gets linked/unlinked?
- What happens when everything is taken to inventory and rezzed back?
- What happens on owner change?
- What happens when a component's description changes after setup?
If I ever get the Falcon working, I'd like to implement some kind of API
that allows outside scripts to hook into the system. The general idea as
of now is to have the controller listen for a subscribe
message on
another channel and add all objects that send such a message to a list
of subscribers. Then, whenever something of relevance happens, the event
can be forwarded to all subscribers. This would allow for people to create
scripts for elevator system status panels, elevator engines, etc.