ID:2012623
 
Understanding the world startup and login process is essential to any project that wants to make efficient use of DM.

Starting up your world:

When you first start a world, the following processes happen:

World initialization process:

1) global variable population

2) globally initialized datum/New() occurs

3) area/New() occurs (1,1,1) to (maxx,maxy,maxz)

4) turf/New() occurs (maxx,maxy,maxz) to (1,1,1)

5) atom/movable/New() occurs (maxx,maxy,maxz) to (1,1,1)

6) world/New() occurs

Areas are initialized from 1,1,1 to the map boundaries unlike the other two processes. Turfs initialize from the end of the map to the start (1,1,1). They don't start until the last painted area initializes. Movables actually do the same, but the first movable doesn't initialize until the last turf initializes.

After the last movable on the map has been initialized, world/New() is called.

Datums work a little bit differently. Datums are initialized in the order that they appear in the source code, but their New() functions are all delayed until the last datum has been initialized.

var
test1/test1 = new()
test2/test2 = new()

test1
New()
test2.testvar++ //logically, this would cause a runtime error because at the point of initialization, test2 hasn't been initialized yet.
//But DM was designed by someone that assumed you didn't want to deal with all that mess!

test2
var
testvar
New()
testvar++


This example works because the world actually initializes global variables all in one pass before the map is initialized. The New() function is called for each initialized global variable AFTER the global variables have all been initialized, but BEFORE the map is initialized.

You can use datum initialization to actually perform work that will affect how the map is initialized. For instance, you could read a configuration file that would cause certain objects to not spawn during map loading even though they are in the DMM.

World/New() is called immediately after map initialization, so any work that depends on atomic objects on the map should be done here. A good use for this would be adding additional work to map initialization. world.time will always be 0 until after world/New() is called, as the global timer doesn't register the world as started until after world/New().


The Login Process:

The login process is something that I've seen as being poorly understood. I see an awful lot of mistakes in character handling in some of the pass-around source code around here, and some of it originates with naive approaches implemented by some of our age-old gurus. Myself included.

The basic login process goes as follows:

1) world/IsBanned()

2) client/New()

3) mob/Login()

Sounds simple, right? Sort of yes, sort of no. Internally, when a client is connected to the world, world/IsBanned() is called, which will return true or a list if the client is in the world's ban configuration file. The list includes three fields: "Login" (1 if the ban allows the user to log in), "reason" (a string containing the reason that they are banned. This is not shown to the user.), "message" (a string that will be shown to the user explaining why they were banned.).

Next, client/New() is called. This method's default behavior is to look for a mob in the world or a prototype mob with the same key as the client. If a mob with the client's key is found, the client will be connected to that mob, calling mob/Login(), and the function will return the found mob. If no mob is found, however, a mob will be created for them from the type specified by world.mob, then the mob will be given a name and gender matching the connecting key. Also, if the TopicData argument is not null, call client.Topic(TopicData). If null is returned from this function, the client will not be allowed to connect to the world and their connection will die instantly.

mob/Login() is called before client/New() returns. This function by default only does two things: first, it attempts to place the mob as near as possible to (1,1,1) if its location is null, iterating through each tile in the world and attempting to move into that tile. The first tile that succeeds will be the player's starting location. Second, the client.statobj variable is changed to this mob.


The Logout Process:

1) client/Del()

2) mob/Logout()

This one's actually really easy. When a client disconnects from the world, client/Del() is called. This function checks to see if the client is connected to a mob and calls mob/Logout(). mob/Logout() by default does nothing, but most games will want to forcibly delete the mob connected to the client here. If the connection is not dead after mob/Logout() returns, the client's connection is killed and the client object is deleted.


The shutdown process:

1) world/Del()

When the world is told to shut down, only the world/Del() function is called. Everything else will be left intact. After the world/Del() function returns, any objects still in memory and any procs currently being called are destroyed silently. No Del() function is called. If you want your players to be saved properly and not lose any data, you need to handle that in world/Del(). If the process of shutting down the world takes longer than 30 seconds, world/Del() will be aborted. It is best not to depend on world/Del() for lengthy processes such as saving the entire map, for instance, because it may not complete properly. It is best to rely on a safe shutdown command if you have drawn out processes such as this.
Things to remember:

1) mob/Login() cannot be relied upon to determine whether or not this is an initial connection to the world. mob/Login() is called any time a client connects to a mob. Don't think just because mob/Login() was called that the client doesn't currently have interface menus open, etc. Don't also assume that the mob the client is connected to needs to be saved under the connected client, because it's possible that the client can be swapping mobs with another player or an NPC. BYOND supports dynamic mob swapping, but doesn't give you the tools out of the box to determine who actually owns a player character.

2) mob/Logout() cannot be relied upon to determine whether or not a client is disconnecting from the world. mob/Logout() is called any time a client disconnects from a mob. Again, BYOND supports dynamic mob swapping. If you want your players to be able to swap characters dynamically or move from mob to mob, you are going to have to develop some sort of a system of identifying mobs. Also don't assume that ckey/key are valid during mob/Logout() for the same reason.

3) You probably don't need the default behavior of mob/Login(). It is probably best to fully override it.
In response to Ter13
Ter13 wrote:
2) mob/Logout() cannot be relied upon to determine whether or not a client is disconnecting from the world. mob/Login() is called any time a client disconnects from a mob.

Great post! Shouldn't the last quoted sentence be "mob/Logout()" instead of login?

Glad to see you back, Ter13!

I'm a big fan of your 'Snippet' series. Keep them coming! :)
Ter13 wrote:
atomic objects

Datums work a little bit differently. Datums are initialized in the order that they appear in the source code, but their New() functions are all delayed until the last datum has been initialized.

I'm just gonna point out, that if you define a datum's New() before world/New(), this stops being true, and all datums are created AND New()'ed in order.
Ter13 wrote:
Datums work a little bit differently. Datums are initialized in the order that they appear in the source code, but their New() functions are all delayed until the last datum has been initialized.

var
test1/test1 = new()
test2/test2 = new()

test1
New()
test2.testvar++ //logically, this would cause a runtime error because at the point of initialization, test2 hasn't been initialized yet.
//But DM was designed by someone that assumed you didn't want to deal with all that mess!

test2
var
testvar
New()
testvar++


I'm just gonna point out that this *does* cause a runtime error;
runtime error: Cannot read .testvar
proc name: New (/test1/New)
usr: null
src: /test1 (/test1)
call stack:
/test1 (/test1): New()
world: ()


The following, however, does not:
test2
New()
testvar++

var
test1/test1 = new()
test2/test2 = new()

test1
New()
test2.testvar++

test2
var
testvar


related: http://www.byond.com/forum/?post=2024969