<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>http://osimdev.org/wiki/index.php?action=history&amp;feed=atom&amp;title=OpenSimulator_Internals%2FCode_Map%2FLLClientView</id>
	<title>OpenSimulator Internals/Code Map/LLClientView - Revision history</title>
	<link rel="self" type="application/atom+xml" href="http://osimdev.org/wiki/index.php?action=history&amp;feed=atom&amp;title=OpenSimulator_Internals%2FCode_Map%2FLLClientView"/>
	<link rel="alternate" type="text/html" href="http://osimdev.org/wiki/index.php?title=OpenSimulator_Internals/Code_Map/LLClientView&amp;action=history"/>
	<updated>2026-08-04T15:27:51Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.45.3</generator>
	<entry>
		<id>http://osimdev.org/wiki/index.php?title=OpenSimulator_Internals/Code_Map/LLClientView&amp;diff=56&amp;oldid=prev</id>
		<title>Jwbshaw: First</title>
		<link rel="alternate" type="text/html" href="http://osimdev.org/wiki/index.php?title=OpenSimulator_Internals/Code_Map/LLClientView&amp;diff=56&amp;oldid=prev"/>
		<updated>2026-07-07T11:41:21Z</updated>

		<summary type="html">&lt;p&gt;First&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;= OpenSimulator Internals/Code Map/LLClientView =&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
&lt;br /&gt;
LLClientView is the concrete implementation of IClientAPI for the Second Life UDP protocol. It sits above LLUDPServer/LLUDPClient and translates parsed LLUDP packets into calls on the many IClientAPI events that Scene and its modules subscribe to, and conversely serializes outgoing scene state into LLUDP packets.&lt;br /&gt;
&lt;br /&gt;
Source: OpenSim/Region/ClientStack/Linden/UDP/LLClientView.cs&lt;br /&gt;
&lt;br /&gt;
Class declaration:&lt;br /&gt;
&lt;br /&gt;
 public class LLClientView : IClientAPI, IClientCore, IClientIM, IClientChat, IClientInventory, IStatsCollector, IClientIPEndpoint&lt;br /&gt;
&lt;br /&gt;
This is the largest single class in the codebase by source volume. It implements roughly 200 IClientAPI events and a dispatch table of over 200 packet handlers.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Construction ==&lt;br /&gt;
&lt;br /&gt;
Constructor takes: Scene, LLUDPServer, LLUDPClient, AuthenticateResponse sessionInfo, agentId, sessionId, circuitCode.&lt;br /&gt;
&lt;br /&gt;
# Registers itself as IClientIM, IClientInventory, IClientChat via RegisterInterface (IClientCore pattern)&lt;br /&gt;
# Creates PriorityQueue for entity updates and entity property updates, sized to min(512, scene entity count)&lt;br /&gt;
# Looks up IAssetService and IGroupsModule from the scene&lt;br /&gt;
# Creates LLImageManager for texture streaming&lt;br /&gt;
# Copies agent identity from sessionInfo (firstname, lastname, session/secure session, start position)&lt;br /&gt;
# Hooks LLUDPClient events: OnQueueEmpty, HasUpdates, OnPacketStats&lt;br /&gt;
# Creates Prioritizer for update priority calculation&lt;br /&gt;
# Calls RegisterLocalPacketHandlers() -- registers the &amp;quot;autopilot&amp;quot; generic message handler&lt;br /&gt;
# Creates a JobEngine for async packet processing (5000ms timeout)&lt;br /&gt;
&lt;br /&gt;
Start() calls m_asyncPacketProcess.Start() then Scene.AddNewAgent(this, PresenceType.User) -- this is what actually creates the ScenePresence.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Packet Dispatch ==&lt;br /&gt;
&lt;br /&gt;
Two dispatch tables, both keyed by PacketType:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Table !! Notes&lt;br /&gt;
|-&lt;br /&gt;
| m_staticHandlers || FrozenDictionary&amp;lt;PacketType, StaticPacketProcessor&amp;gt; -- built once at class load via a static initializer covering ~230 packet types. Static methods take (LLClientView, Packet) so no per-instance delegate allocation.&lt;br /&gt;
|-&lt;br /&gt;
| m_packetHandlers || Instance Dictionary&amp;lt;PacketType, PacketProcessor&amp;gt; -- populated by AddLocalPacketHandler(), used by region modules to add handlers without modifying this file&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
ProcessPacketMethod() checks m_staticHandlers first, then m_packetHandlers. Each entry carries an Async flag -- if true, the handler runs on the JobEngine (m_asyncPacketProcess) rather than the shared IncomingPacketHandler thread from LLUDPServer, so a slow handler for one client doesn&amp;#039;t block packet processing for others.&lt;br /&gt;
&lt;br /&gt;
ProcessInPacket() is the actual entry point called by LLUDPServer.IncomingPacketHandler() (via IncomingPacket.Client.ProcessInPacket()). Falls through to a warning log if no handler is registered for a packet type.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Outbound: Entity Updates ==&lt;br /&gt;
&lt;br /&gt;
This is the most performance-critical part of the class -- converting scene object and avatar state into the LLUDP wire format every frame.&lt;br /&gt;
&lt;br /&gt;
=== Update Queueing ===&lt;br /&gt;
&lt;br /&gt;
SendEntityUpdate(entity, flags) computes a priority via Prioritizer.GetUpdatePriority() and enqueues an EntityUpdate into m_entityUpdates (a PriorityQueue). Kill flags and HUD-to-non-owner filtering happen here before queueing.&lt;br /&gt;
&lt;br /&gt;
=== ProcessEntityUpdates ===&lt;br /&gt;
&lt;br /&gt;
Called from HandleQueueEmpty() (LLUDPClient throttle callback) with a byte budget. Dequeues from m_entityUpdates until the budget is exhausted or the queue is empty, sorting each update into one of several packet-type buckets:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Bucket !! Trigger !! Notes&lt;br /&gt;
|-&lt;br /&gt;
| terseUpdates || Update only touches position/rotation/velocity/attachment/collision plane/textures (canNotUseImprovedMask check) || Cheapest wire format -- ImprovedTerseObjectUpdate&lt;br /&gt;
|-&lt;br /&gt;
| objectUpdates || ScenePresence full updates, or object full updates when not viewer-cachable || ObjectUpdate packet, CreatePrimUpdateBlock/CreateAvatarUpdateBlock&lt;br /&gt;
|-&lt;br /&gt;
| compressedUpdates || Object full updates when viewer object caching is supported and object IsViewerCachable || Smaller wire format for objects the viewer may already have cached&lt;br /&gt;
|-&lt;br /&gt;
| objectUpdateProbes || PrimUpdateFlags.UpdateProbe set || ObjectUpdateCached packet -- asks viewer &amp;quot;do you already have this by CRC&amp;quot; before sending a full update&lt;br /&gt;
|-&lt;br /&gt;
| ObjectAnimationUpdates || PrimUpdateFlags.Animations set and viewer supports object animations || Separate ObjectAnimation packet per part&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Per-object filtering before bucketing: skip if grp.inTransit, grp.IsDeleted (send kill instead), attachment owned by someone else&amp;#039;s HUD, attachment owner not found or still child agent, attachment not actually in the owner&amp;#039;s attachment list, distance culling (ObjectsCullingByDistance) against DrawDistance + ReprioritizationDistance + 16m.&lt;br /&gt;
&lt;br /&gt;
All buckets pack into UDPPacketBuffer using LLUDPZeroEncoder inline (avoids intermediate byte[] allocation), splitting into additional packets when MAXPAYLOAD is approached mid-loop.&lt;br /&gt;
&lt;br /&gt;
=== Distance Culling (CheckGroupsInView) ===&lt;br /&gt;
&lt;br /&gt;
When ObjectsCullingByDistance is enabled, this recomputes which SceneObjectGroups are within cullingrange (DrawDistance + ReprioritizationDistance + 16m) of the avatar every reprioritization cycle. Groups that fall out of range get a Kill sent; groups newly in range get a full update queued (with UpdateProbe if viewer caching is supported and the handshake flag for it hasn&amp;#039;t been sent yet).&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Region Handshake ==&lt;br /&gt;
&lt;br /&gt;
SendRegionHandshake() builds the RegionHandshake packet by hand using LLUDPZeroEncoder rather than the generic Packet classes, for performance. Contents: region flags (GetRegionFlags(), a large bitwise translation of RegionSettings/EstateSettings into the wire RegionFlags enum), sim access level, region name, estate owner, water height, billable factor, cache ID, four terrain texture or PBR texture IDs (SupportTerrainPBR flag chooses which), elevation blend values per corner, region ID, CPU class/ratio (hardcoded 9/1), product name, and extended region flags (64-bit) plus a RegionProtocols bitfield (bit 0 = server-side baking supported, bit 63 = more than 6 baked textures supported).&lt;br /&gt;
&lt;br /&gt;
MoveAgentIntoRegion() sends AgentMovementComplete -- also hand-packed -- confirming the avatar&amp;#039;s position, look direction, region handle, and simulator version string after CompleteAgentMovement is processed.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Connection Lifecycle ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Method !! Notes&lt;br /&gt;
|-&lt;br /&gt;
| Close(sendStop, force) || Locked by CloseSyncLock to prevent races with relogin. Sends DisableSimulator if sendStop. Fires OnConnectionClosed. Stops async job engine. Flushes UDP server. Calls Scene.RemoveClient(). Unhooks UDP client events. Closes ImageManager, entity queues. Forces GC compaction if this was the last client in the scene.&lt;br /&gt;
|-&lt;br /&gt;
| Kick(message) || Sends KickUser packet to root agents only, sleeps 500ms to let it land before the connection is torn down&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Handling UseCircuitCode ==&lt;br /&gt;
&lt;br /&gt;
Note: the actual UseCircuitCode packet is handled in LLUDPServer.HandleUseCircuitCode() (see [[OpenSimulator Internals/Code Map/LLUDP]]), not here. The HandleUseCircuitCode static method inside LLClientView&amp;#039;s own dispatch table is a no-op (entire body commented out) -- retained only so the packet type does not fall through to the &amp;quot;unhandled packet&amp;quot; warning.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Key Handler Categories ==&lt;br /&gt;
&lt;br /&gt;
The packet handler methods (all static, taking (LLClientView c, Packet packet)) are organized into named regions in source. Notable groups:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Region !! Representative packets !! Notes&lt;br /&gt;
|-&lt;br /&gt;
| Scene/Avatar || AgentUpdate, SetAlwaysRun, AgentAnimation, AgentFOV, AgentThrottle, AgentPause/Resume || HandleAgentUpdate implements significance thresholds (QDELTABody/Head, VDELTA) so trivial camera/body jitter below a cosine threshold does not trigger a full event dispatch -- see CheckAgentMovementUpdateSignificance / CheckAgentCameraUpdateSignificance&lt;br /&gt;
|-&lt;br /&gt;
| Objects/m_sceneObjects || ObjectLink, ObjectDelink, ObjectAdd, ObjectShape, ObjectDuplicate, ObjectGrab/GrabUpdate/DeGrab, ObjectSpinStart/Update/Stop, MultipleObjectUpdate || HandleMultipleObjUpdate decodes a compact bitfield (position/rotation/scale, individual vs group, uniform-scale) into ObjectChangeType values for onClientChangeObject&lt;br /&gt;
|-&lt;br /&gt;
| Inventory/Asset || CreateInventoryFolder/Item, FetchInventoryDescendents, UpdateInventoryItem, RezScript, TransferRequest || HandleTransferRequest spawns a background permission check (HandleSimInventoryTransferRequestWithPermsCheck) for SimInventoryItem source type before calling MakeAssetRequest&lt;br /&gt;
|-&lt;br /&gt;
| Parcel || ParcelPropertiesRequest, ParcelDivide/Join, ParcelAccessListUpdate, ParcelReturnObjects, LandStatRequest || &lt;br /&gt;
|-&lt;br /&gt;
| Estate || EstateOwnerMessage (itself a sub-dispatch on a string method name: getinfo, setregioninfo, texturedetail, restart, estateaccessdelta, terrain, telehub, refreshmapvisibility, etc.), EstateCovenantRequest ||&lt;br /&gt;
|-&lt;br /&gt;
| Groups || CreateGroupRequest, GroupRoleDataRequest, GroupMembersRequest, JoinGroupRequest, InviteGroupRequest || Delegates to IGroupsModule; returns early if module is not loaded&lt;br /&gt;
|-&lt;br /&gt;
| God || RequestGodlikePowers, GodKickUser, GodUpdateRegionInfo || &lt;br /&gt;
|-&lt;br /&gt;
| Money/Economy || MoneyTransferRequest, MoneyBalanceRequest, ObjectBuy, ParcelBuy ||&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Notable Implementation Details ==&lt;br /&gt;
&lt;br /&gt;
* AgentUpdate packets arrive roughly 10x/second per client even when idle. HandleAgentUpdate filters for significance (movement flags, body rotation beyond a near-1.0 cosine threshold, camera position/axis beyond a 0.01 delta) before invoking OnAgentUpdate/OnAgentCameraUpdate, and forces a full update at least every 500ms regardless.&lt;br /&gt;
* Sequence-number gating: HandleAgentUpdate drops any AgentUpdate whose sequence number is not strictly greater than the last one processed, to avoid reordered/duplicate UDP packets corrupting movement state.&lt;br /&gt;
* ObjectImage (texture updates) tracks a per-prim sequence number in objImageSeqs to discard stale/duplicate texture-set requests, expiring the tracking dictionary every 30 seconds.&lt;br /&gt;
* DeRezObject supports multi-packet reassembly (m_DeRezObjectDelayed) when a single de-rez request is split across packets by the viewer.&lt;br /&gt;
* CreateCompressedUpdateBlockZC has a large commented-out non-zero-encoded twin (CreateCompressedUpdateBlock) retained in source as a reference/fallback that is not currently compiled in.&lt;br /&gt;
* GetViewerCaps() reads capability flags from the CAPS module (SentSeeds, ObjectAnim, WLEnv/AdvEnv for windlight environment support, PBR, TPBR for terrain PBR) and caches which optional features (m_SupportObjectAnimations, m_SupportPBR, SupportTerrainPBR) this specific viewer connection supports, gating several of the update-encoding code paths above.&lt;br /&gt;
* SendEstateList, SendBannedUserList and related estate list sends batch at 63 entries per EstateOwnerMessage packet to stay under datagram size.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== See Also ==&lt;br /&gt;
&lt;br /&gt;
* [[OpenSimulator Internals/Code Map/LLUDP]]&lt;br /&gt;
* [[OpenSimulator Internals/Code Map/Scene]]&lt;br /&gt;
* [[OpenSimulator Internals/Code Map/OpenSim]]&lt;br /&gt;
* [[OpenSimulator Internals/Walkthroughs/Bring Up Region]]&lt;/div&gt;</summary>
		<author><name>Jwbshaw</name></author>
	</entry>
</feed>