<?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%2FROBUST%2FGridUserService</id>
	<title>OpenSimulator Internals/Code Map/ROBUST/GridUserService - 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%2FROBUST%2FGridUserService"/>
	<link rel="alternate" type="text/html" href="http://osimdev.org/wiki/index.php?title=OpenSimulator_Internals/Code_Map/ROBUST/GridUserService&amp;action=history"/>
	<updated>2026-08-04T15:31:45Z</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/ROBUST/GridUserService&amp;diff=63&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/ROBUST/GridUserService&amp;diff=63&amp;oldid=prev"/>
		<updated>2026-07-07T12:02:15Z</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/ROBUST/GridUserService =&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
&lt;br /&gt;
GridUserService tracks per-user grid state: online/offline status, login/logout timestamps, home location, and last known position. It is used by LoginService on login and logout, and by simulators to update position as the avatar moves.&lt;br /&gt;
&lt;br /&gt;
Source files:&lt;br /&gt;
&lt;br /&gt;
 OpenSim/Services/UserAccountService/GridUserService.cs&lt;br /&gt;
 OpenSim/Services/UserAccountService/GridUserServiceBase.cs&lt;br /&gt;
&lt;br /&gt;
Note: despite the name, these files live in the UserAccountService directory, not a GridUserService directory.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== Constructor and Config Loading ===&lt;br /&gt;
&lt;br /&gt;
 GridUserServiceBase(IConfigSource config)&lt;br /&gt;
&lt;br /&gt;
Config is read in two layers; [GridUserService] overrides [DatabaseService]:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Section !! Key !! Default !! Notes&lt;br /&gt;
|-&lt;br /&gt;
| [DatabaseService] || StorageProvider || (none) || DLL name -- fallback&lt;br /&gt;
|-&lt;br /&gt;
| [DatabaseService] || ConnectionString || (none) || DB connection string -- fallback&lt;br /&gt;
|-&lt;br /&gt;
| [GridUserService] || StorageProvider || (inherited) || Overrides [DatabaseService]&lt;br /&gt;
|-&lt;br /&gt;
| [GridUserService] || ConnectionString || (inherited) || Overrides [DatabaseService]&lt;br /&gt;
|-&lt;br /&gt;
| [GridUserService] || Realm || GridUser || Table name&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Note: the comment in GridUserServiceBase.cs misspells the section name as [GridUsetService]. The actual section key read is &amp;quot;GridUserService&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Throws if StorageProvider is empty or if the plugin cannot be loaded.&lt;br /&gt;
&lt;br /&gt;
The loaded plugin is stored as m_Database (IGridUserData).&lt;br /&gt;
&lt;br /&gt;
Console commands are registered once only (m_Initialized static flag prevents re-registration on multiple instantiations):&lt;br /&gt;
&lt;br /&gt;
* show grid user &amp;lt;ID&amp;gt; -- displays all fields for a user ID (prefix match via GetAll)&lt;br /&gt;
* show grid users online -- counts users marked Online whose last login is less than 5 days ago&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== Data Model ===&lt;br /&gt;
&lt;br /&gt;
GridUserData stores one row per user. Fields stored in the Data dictionary:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Field !! Type !! Notes&lt;br /&gt;
|-&lt;br /&gt;
| Online || bool string || &amp;quot;True&amp;quot; or &amp;quot;False&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
| Login || int string || Unix timestamp of last login&lt;br /&gt;
|-&lt;br /&gt;
| Logout || int string || Unix timestamp of last logout&lt;br /&gt;
|-&lt;br /&gt;
| HomeRegionID || UUID string || Region UUID of home location&lt;br /&gt;
|-&lt;br /&gt;
| HomePosition || Vector3 string || Position within home region&lt;br /&gt;
|-&lt;br /&gt;
| HomeLookAt || Vector3 string || Look-at direction at home&lt;br /&gt;
|-&lt;br /&gt;
| LastRegionID || UUID string || Region UUID of last known position&lt;br /&gt;
|-&lt;br /&gt;
| LastPosition || Vector3 string || Last known position within region&lt;br /&gt;
|-&lt;br /&gt;
| LastLookAt || Vector3 string || Last known look-at direction&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
UserID is stored directly on GridUserData, not in the Data dictionary.&lt;br /&gt;
&lt;br /&gt;
UserID may be longer than 36 characters (HyperGrid user IDs include a URI suffix). All cache and lookup operations truncate to the first 36 characters for the cache key.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== Cache ===&lt;br /&gt;
&lt;br /&gt;
 private static ExpiringCacheOS&amp;lt;string, GridUserData&amp;gt; cache&lt;br /&gt;
&lt;br /&gt;
Static cache shared across all instances. TTL is 300000ms (5 minutes). Cache key is the first 36 characters of userID.&lt;br /&gt;
&lt;br /&gt;
GetGridUserData() checks the cache first. On cache miss, calls m_Database.GetAll(userID). All write operations (LoggedIn, LoggedOut, SetHome, SetLastPosition) update the cache after a successful Store().&lt;br /&gt;
&lt;br /&gt;
If GetAll() returns multiple records for the same userID (should not happen in a healthy database), GetGridUserData() selects the record with the most recent Login or Logout timestamp. Parse failures during this selection are silently swallowed via empty catch.&lt;br /&gt;
&lt;br /&gt;
Null results are cached for 300000ms to avoid repeated database hits for unknown users.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== GetGridUserInfo ===&lt;br /&gt;
&lt;br /&gt;
 public virtual GridUserInfo GetGridUserInfo(string userID)&lt;br /&gt;
 public virtual GridUserInfo[] GetGridUserInfo(string[] userIDs)&lt;br /&gt;
&lt;br /&gt;
Single-user: calls GetGridUserData(), returns null if not found, otherwise converts via ToInfo().&lt;br /&gt;
&lt;br /&gt;
Batch: iterates the array and calls the single-user method for each. No batch database query -- each ID hits the cache or database individually.&lt;br /&gt;
&lt;br /&gt;
ToInfo() maps GridUserData fields to GridUserInfo:&lt;br /&gt;
* Missing or unparseable fields are left as default values (zero UUID, zero Vector3, UnixEpoch for timestamps)&lt;br /&gt;
* Login and Logout are converted from Unix int to DateTime via Util.ToDateTime()&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== LoggedIn ===&lt;br /&gt;
&lt;br /&gt;
 public GridUserInfo LoggedIn(string userID)&lt;br /&gt;
&lt;br /&gt;
# Fetches existing record or creates a new GridUserData if none exists&lt;br /&gt;
# Sets Online = &amp;quot;True&amp;quot;&lt;br /&gt;
# Sets Login = current Unix timestamp&lt;br /&gt;
# Stores to database&lt;br /&gt;
# Updates cache&lt;br /&gt;
# Returns GridUserInfo via ToInfo()&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== LoggedOut ===&lt;br /&gt;
&lt;br /&gt;
 public bool LoggedOut(string userID, UUID sessionID, UUID regionID, Vector3 lastPosition, Vector3 lastLookAt)&lt;br /&gt;
&lt;br /&gt;
sessionID parameter is accepted but not used.&lt;br /&gt;
&lt;br /&gt;
# Fetches existing record or creates a new GridUserData if none exists&lt;br /&gt;
# Sets Online = &amp;quot;False&amp;quot;&lt;br /&gt;
# Sets Logout = current Unix timestamp&lt;br /&gt;
# Sets LastRegionID, LastPosition, LastLookAt from parameters&lt;br /&gt;
# Stores to database -- returns false on Store() failure&lt;br /&gt;
# Updates cache on success&lt;br /&gt;
# Returns true on success, false on Store() failure&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== SetHome ===&lt;br /&gt;
&lt;br /&gt;
 public bool SetHome(string userID, UUID homeID, Vector3 homePosition, Vector3 homeLookAt)&lt;br /&gt;
&lt;br /&gt;
# Fetches existing record or creates a new GridUserData if none exists&lt;br /&gt;
# Sets HomeRegionID, HomePosition, HomeLookAt from parameters&lt;br /&gt;
# Stores to database -- returns false on Store() failure&lt;br /&gt;
# Updates cache on success&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== SetLastPosition ===&lt;br /&gt;
&lt;br /&gt;
 public bool SetLastPosition(string userID, UUID sessionID, UUID regionID, Vector3 lastPosition, Vector3 lastLookAt)&lt;br /&gt;
&lt;br /&gt;
sessionID parameter is accepted but not used.&lt;br /&gt;
&lt;br /&gt;
# Fetches existing record or creates a new GridUserData if none exists&lt;br /&gt;
# Sets LastRegionID, LastPosition, LastLookAt from parameters&lt;br /&gt;
# Stores to database -- returns false on Store() failure&lt;br /&gt;
# Updates cache on success&lt;br /&gt;
&lt;br /&gt;
The debug log line for this method is commented out in the source.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== Console Commands ===&lt;br /&gt;
&lt;br /&gt;
show grid user &amp;lt;ID&amp;gt;:&lt;br /&gt;
* Calls m_Database.GetAll(ID) -- prefix match&lt;br /&gt;
* Displays all fields for each matching record via ConsoleDisplayList&lt;br /&gt;
* Prints count of matching entries&lt;br /&gt;
&lt;br /&gt;
show grid users online:&lt;br /&gt;
* Iterates all records via m_Database.GetAll(&amp;quot;&amp;quot;)&lt;br /&gt;
* Counts records where Online == &amp;quot;True&amp;quot; AND login timestamp is less than 5 days ago&lt;br /&gt;
* The 5-day cutoff exists because crashed or unclean-shutdown simulators may leave users marked online indefinitely&lt;br /&gt;
* The commented-out code shows a total online count was originally planned alongside the recent count&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== Notes ===&lt;br /&gt;
&lt;br /&gt;
* The comment in GridUserServiceBase.cs misspells [GridUserService] as [GridUsetService] -- the code reads the correct key name regardless.&lt;br /&gt;
* sessionID is accepted by LoggedOut() and SetLastPosition() but never used.&lt;br /&gt;
* The batch GetGridUserInfo(string[]) does not batch database queries -- it calls the single-user path per ID.&lt;br /&gt;
* Multiple records per userID are handled defensively but should not occur in a healthy database.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== See Also ==&lt;br /&gt;
&lt;br /&gt;
* [[OpenSimulator Internals/Code Map/ROBUST]]&lt;br /&gt;
* [[OpenSimulator Internals/Code Map/Shared]]&lt;br /&gt;
* [[OpenSimulator Internals/Databases]]&lt;/div&gt;</summary>
		<author><name>Jwbshaw</name></author>
	</entry>
</feed>