The online racing simulator
Overview of InSim Systems
(18 posts, started )
Overview of InSim Systems
I've realized that no one has posted an overview of the current InSim system, in fact the last packet level overview was from a point of view of version 3. We are on version 4 at time of writing, with an current LFS version of S2 Z28.

What Scawen has given us, seems to me at least, to be hooks into certain parts of the LFS engine's data bank. But no one wishes to make claims to how these system are meant to interact with each other. For example, we all bitch and complain about PLID and UCID (I know I do all of the time. It's hard to make a generic interface, when you don't have a generic way of accessing the information on the client. PLID in one packet, UCID in another, what's the point of these two IDentifiers? Well one connects you to the PLayer system that handles car and camera, and is the entity of the client within the game world where it be the client viewing another car, or the client racing said car. The user system is more about record keeping of the client. Holding here is just the client's UName (UserName or LFS World License Name) and PName or PlayerName, an alias for the client at game time.

So we have the Player class with the sub double role of keeping the clients information within the game world both though it's camera state and the client's car on the track. Where as the User class keep track of the basic information on the client.

I would love to have some real authoritative background information on InSim, and find out how far off I am from how the devs feel about the use of the insim system.
In my InSim applications I have a Connection/Driver manager. It allows only one -PLAYER- per connection. (With the exception of my AI project which is different altogether). I always use the UCID whereever possible, however more importantly almost all my lookups are done via username, which will get me the correct UCID or PLID (even if disconnect/reconnect, pit/spec/join had occurred). I use the username because it is unique _always_ for each individual racer, and it does not change for that racer. If a username is not connected for 10 minutes (on my casual systems that I've made) I throw the information about the user out of memory, saving what is needed to file/disk etc...

This was the easiest, most flexible way I found of doing this for Dash For Cash, and Head-to-Head. Both of which needed to keep information about a driver in the event of a disconnect/reconnect. By using the username I can get all information about the player; their current state, Playing, Spectating. Their Player Name (used for all LFS Displays), their UCID and PLID. Sure the downfall is a string comparison instead of a byte == byte comparison but for the flexibility, ease of use and everything this is well worth it - IMO. It would always be possible to make a new ID for my application to remove the need of string comparisons, but that has always been overkill IMO for something that runs with event-based structure. (I don't collect NLP / MCI messages during these applications, and if I was to start I would likely do my above example and create my own ID that stays the same for each racer.

I think that is what you were asking... It is how I work the system anyway during my casual and league systems.
[off-topic]

So you come up with a id that's non-instanced? In that it stays with that client for the lifetime of the apps run time. Should the app say run for 100 days, and the client come on 4 days after the app first starts then they are assigned an id to be used to reference that client from then on. Because that would be a cool way of getting the last time a client was on the server last or if they have never been there before. You've given me a very cool idea for a plugin. While the insim system does not have to do this by it's self. we could keep a persistent id for each client for use of accessing client information while they are offline.

God I really want access to LFS World's SQL id name for ... wait I have that! It's my user id on here! Hah!

[/off-topic]

God, my mind is really active today. I know you misunderstood my intent of this, but that does not matter because you gave me a really cool idea, and I like the direction this thread is going.


How does everyone handle the InSim system?
That would be why I use the username, it is the one constant, that is always different for each user. I would not recommend keeping your own unique ID for 100 days per client... Instead, after 10 to 15 minutes of the client being, and staying disconnected from the server (perhaps longer if desired) be sure to remove the information - saving what is necessary (not the ID directly, although you can do that to if you make a unique id per username, although I prefer to keep the username the unique constant. (Did that make sense?).

The reason is simple, say you have your application on a public server that has been running for 100days, (over three months). Think of the number of different users that have connected and disconnected. Now for giggles, take that number an multiply by the amount of information you keep for each client; timeLastOnline, numLapsDriven, playerName, etc etc... It will be a lot of memory, and a lot that could be safely saved and stored until the next time that user jumps online, even if it is 20 minutes later.

But my first post describes the way I've used it, I would like to see other thoughts/takes on it. Though, my LFS to AIRS project uses it in slightly different way.

Connecting only to local copy, no servers. Making sure that only 1 connection exists (UCID = 0) and a few other things. Then I need to find the player's carm as that is the car my AI Controller can control and I need to save / update the PLID whenever it changes. Then I use MCI and OutGauge packets to update the AI by converting that information to a way that AIRS excepts and sending it to the AIRS project. Though, this is likely so far different than a server system, or even any other single player application that it doesn't exactly apply.
Quote :// RACE TRACKING
// =============

// In LFS there is a list of connections AND a list of players in the race
// Some packets are related to connections, some players, some both

// If you are making a multiplayer InSim program, you must maintain two lists
// You should use the unique identifier UCID to identify a connection

// Each player has a unique identifier PLID from the moment he joins the race, until he
// leaves. It's not possible for PLID and UCID to be the same thing, for two reasons :

// 1) there may be more than one player per connection if AI drivers are used
// 2) a player can swap between connections, in the case of a driver swap (IS_TOC)

I see no problem with this. The UCID is session-unique, it will be assigned to a connection when said connection is established and it will remain static until the connection is disconnected. Except for the PName, all information contained in the NCN is also static, so all you have to do with regards to your connections list is listen for CPR and modify the PName upon receiving one.

Now this might be confusing, because CPR stands for Conn Player Rename, yet the CPR packet does not contain a PLID, only a UCID. This is because there can be only one human player and that's the only player of a connection capable of renaming. AIs have to be removed from the race, then renamed, then sent back out again.

So how can I find out who the human is if multiple players for a connection exist? Easy:
NPL.PType & 2

What I do is keep a list of PLIDs with every connection, in python via dict which allows me to use the PLID as index and a reference to the player as value, the human player is kept in addition to allow quick access to the player.
This allows me to iterate over the list if I need to do something with all players of a connection, or directly access the human player for whatever I have to do that. An example being aforementioned CPR, which as we established only contains a UCID and no PLID. So what you do when you receive a CPR is you dereference your human player from your connection list and change the name there as well. A bit "wonky", but manageable

When information has to be kept over multiple sessions, the username is indeed the only globally unique identifier to rely on, but that doesn't mean you have to use it within a working session. Upon connecting, the user's data is obtained from the database and assigned to the UCID, much faster than comparing the string every time.
I agree UCID is a faster comparison, as I mentioned above that was the downer for doing it my way, but when your checking events; Grid Reorder, Race Finish, Joining/Spectating then the time wasted in string compare is a fine sacrifice. I consider a 'session' the amount of time my application is running, not the amount of time that a client is connected to the server. Therefor the UCID changes during a session, in my eyes. And whereas I have a solution, by creating a third id unique to my program for each client - I haven't seen the need - though I see the need to if using that for a real-time update (MCI etc... or just running a real-time application instead of event based that runs at 15fps at most).

CPR.PType ?? What is this, I don't have any documentation on it.
Quote from blackbird04217 :CPR.PType ?? What is this, I don't have any documentation on it.

Sorry, that should have been NPL.
The second bit (aka bit 1) of the PType in an NPL indicates whether the player is an AI or not.
PLID can be from 0 to 42?
UCID can be from 0 to 64?
I've always assumed they could get to 255. I guess it depends how fast people are joining/spectating or connecting/disconnecting. I wouldn't go assuming those values though, since the type is a byte the value can range between 0 and 255 even if in practice you rarely see anything larger than what you say. (Unless you have significantly trustworthy documentation, although I still don't know why you'd put in the assumption since it is a byte still... My thoughts / opinions.

How is your C++ practice going?
Quote from blackbird04217 :I've always assumed they could get to 255. I guess it depends how fast people are joining/spectating or connecting/disconnecting. I wouldn't go assuming those values though, since the type is a byte the value can range between 0 and 255 even if in practice you rarely see anything larger than what you say. (Unless you have significantly trustworthy documentation, although I still don't know why you'd put in the assumption since it is a byte still... My thoughts / opinions.

This is true, that value has the potential of going from 0 - 255, but I've yet to see it go past 42 for PLID and I'm pretty sure UCID is maxed out at 64, as that's the max clients for the server. I ask because I want to know how large to make each array, and get an idea of the max memory.

Quote from blackbird04217 :How is your C++ practice going?

Put on a back burner, until I can figure out what I really want from it. I'm going to protype the project in PHP and then move it over to C++ for the true implementation of what I wanted to do (PAWN Plugins for InSim). I'll use PHP to see if the plugin system I have in mind is any good, and if it's not I'll tweak it in there until I get the desired result from the community.
I've usually used dynamic arrays, or vectors (std::vector in C++) for tracking my UCIDs and PLIDs. This is an array that grows and shrinks as more/less players/connections come to the server. Though really it is a little more complicated because my 'DriverManagementSystem' which handles new connections, renaming, joining/spectating etc does all this behind the scenes for me and it's been forever since I touched that code (2yrs I think?). But I remember doing something where the PLID and UCID knew about eachother and I think I actually had a list of PLIDs for each connection since there can be multiple.

This way it only takes the memory needed. You might be right with the UCID thing, but if by chance LFS moved the limit to 64 in race and more for spectating what would happen to your code? It'd break, and InSim likely wouldn't need to be updated for that switch to happen. Hope this helps with array stuff, I don't know about dynamically sizing arrays in php because I don't know php : / but I know all about it in C/C++!
Wouldn't it be possible to use a std::map, as that would allow you to associate key/value pairs, so you could have the PLID as the key and a Player object as the value. Then it's very easy and fast to look a player up, also you won't need to worry about how big to make your array.

Edit: Note - In .NET and Python a map is known as a dictionary.
Yes that would have been my recommendation, but Dygear wanted a low-level, constant access time implementation (a hashtable essentially). Vectors would allow that but at no benefit over a pre-allocated array.
(This whole post refers to the C++ code only)

Like morpha said, I was going to make a 'hashtable', where you use an ID or UCID to get a clients' information. So, if you where to use get_user_pname, to get the clients player name, and there are 16 players in the game. You could use this:

/* Returns a string of 24 chars in lenght.
** Requires the UCID of the client to get the name of.
*/
get_user_pname(uint8 UCID);

clientConnect(UCID) {
client_print(255, PRINT_CHAT, "Welcome %s (UCID).", get_user_pname(UCID), UCID);
}

How the get_user_pname function would work is this:
Get the pointer to the address where all of the clients's (user's) information are stored.
Get the member offset for pname within the struct.
Read from PointerAddress + PNameMemberOffset to (PointerAddress + PNameMemberOffset) + PNameLen * DataTypeStepSize.

Where PointerAddress equals 0x00555341. (5,591,873 Bytes In)
Where PNameMemberOffset equals 0x00000024. (36 Bytes In)
Where PNameLen equals 0x00000018 (24 Bytes)
Where DataTypeStepSize equals 0x00000001 (1 Byte)

So, we read from 0x00555365 - 0x0055537C, and return that. This is done extremely fast, with very little overhead, and very little code. In find it the perfect solution for a plugin system that's constantly polling stale data. We don't do live requests for data, we just return the data we already have on the client. Because that data is still correct, unless we missed a packet. Effectively, we are recreating the data tables that Live For Speed has, but within the InSim app.
I cannot express how little I care about the performance difference between an array verses a map. I can tell you that what turns out to be the slowest part of your program will have nothing to do with either. Just use the structure that makes the most sense and worry about being slow when you become slow...

(also dictionary lookups are very, very fast!)
Quote from DarkTimes :I cannot express how little I care about the performance difference between an array verses a map.

I don't know why DarkTimes, but I found that quite funny.

I did not choose maps, because I have no idea how to use them. I know how to use pointers, and I know how to add memory address together, so I did that. That fact that it might be faster, was secondary to be getting to the goal line.

I program what I know, and as I've done about 400 lines of C++ in my life up until this point, what I know is very little indeed.
If that's the reason, I'd urge you to simply read up on STL containers and use them instead of (C-ish) pointer arithmetics. They're very easy to use, practically like an array (including the [] operator, which they overload)
Quote from morpha :If that's the reason, I'd urge you to simply read up on STL containers and use them instead of (C-ish) pointer arithmetics. They're very easy to use, practically like an array (including the [] operator, which they overload)


Checking whether build environment is sane ... build environment is grinning and holding a spatula. Guess not.

Overview of InSim Systems
(18 posts, started )
FGED GREDG RDFGDR GSFDG