ifdef::env-github[]
:tip-caption: :bulb:
:note-caption: :information_source:
:important-caption: :heavy_exclamation_mark:
:caution-caption: :fire:
:warning-caption: :warning:
endif::[]
:toc: left
:toclevels: 5

= Developer documentation

== Concepts

=== OOP

vRP 2 Object-Oriented Programming uses the https://github.com/ImagicTheCat/Luaoop[Luaoop] library.

NOTE: By using OOP, instead of loading resources communicating with vRP through Proxy, extensions can directly be loaded into the vRP resource context. Each extension keeps its data and is able to access other extensions data and methods, making extending vRP easier, more powerful (we can access stuff even if not exposed) and with less overhead.

=== Asynchronous

vRP 2 extensively uses asynchronous tasks in a transparent way using coroutines, some functions may not return immediately (or never).

=== DB driver

A DB driver is a class handling MySQL queries. When the vRP MySQL API is used, the DB driver methods are called to process the queries. If the DB driver is not loaded yet, queries will be cached until loaded.

=== Proxy and Tunnel

The proxy library is used to call other resources functions through a proxy event.

The idea behind tunnel is to easily access any exposed server function from any client resource, and to access any exposed client function from any server resource.

TIP: Good practice is to get the interface once and set it as a global. If we want to get multiple times the same interface from the same resource, we need to specify a unique identifier (the name of the resource + a unique id for each one).

NOTE: Tunnel and Proxy are blocking calls in the current coroutine until the values are returned, to bypass this behaviour, especially for the Tunnel to optimize speed (ping latency of each call), use `+_+` as a prefix for the function name (Proxy/Tunnel interfaces should not have functions starting with `+_+`). This will discard the returned values, but if we still need them, we can make normal calls in a new Citizen thread with `Citizen.CreateThreadNow` or `async` to have non-blocking code.

WARNING: Also remember that Citizen event handlers (used by Proxy and Tunnel) may not work while loading the resource; to use the Proxy at loading time, we will need to delay it with `Citizen.CreateThread` or a `SetTimeout`.

=== Data

==== Multi-server and character

vRP 2 has multi-server and multi-character support. Each server has a string identifier.

WARNING: Players can use another character after spawned, thus extensions should properly handle character load/unload events and check if the character is ready.

==== Key-value stores

vRP provides a key-value API to store custom binary data in the database. A string key is associated to a binary value (ex: a Lua string). The key is a `VARCHAR(100)` and the value a `BLOB` (probably 65535 bytes).

.Types
[horizontal]
global:: GData
per server:: SData
per user:: UData
per character:: CData

CAUTION: vRP core tables, as core files, should not be modified for extensions compatibility. New tables and queries should be created if the key-value system is not powerful enough.

==== Core

vRP core modules data may be stored in specific MySQL tables or using the key-value stores. For the latter, the keys will probably be prefixed by `vRP` and the binary data will use the https://msgpack.org[MessagePack] format.

=== Script loading

To use vRP 2, a script must be loaded in the vRP resource context of a specific side.

[source,lua]
----
-- include `@vrp/lib/utils.lua` in `__resource.lua` (for the targeted side)

local Proxy = module("vrp", "lib/Proxy")
local vRP = Proxy.getInterface("vRP")

vRP.loadScript("my_resource", "vrp") -- load "my_resource/vrp.lua"
----

The content of `vrp.lua` is now executed in the vRP context and can now use the API.

NOTE: `vRP.loadScript()` is a proxy to call `module()` in the vRP context.

=== Extension

An extension is a class extending vRP.

Two versions of the same extension (same name) can be loaded: for the server-side and the client-side. They will be able to interact with each other through the `tunnel`/`remote` interfaces.

[source,lua]
----
local MyExt = class("MyExt", vRP.Extension)
----

Loaded extensions are accessibles through the vRP instance:
[source,lua]
----
vRP.EXT.MyExt:test()
----

TIP: You can see how an extension is made by looking at the code of vRP link:../../vrp/modules[modules] or https://github.com/ImagicTheCat/vRP-basic-mission.

==== User

Extensions can extend User properties/methods with a User class (constructor is called).

WARNING: To not conflict with other extensions, make sure the added properties and methods have a very specific name or prefix.

[source,lua]
----
MyExt.User = class("User")
----

==== Event

Extensions can listen to global events by defining methods in the `event` table.

[source,lua]
----
MyExt.event = {}

function MyExt.event:playerSpawn(user, first_spawn)
end
----

NOTE: Events marked with `(sync)` in the documentation may be called using `vRP:triggerEventSync` which will wait for the listeners to complete, meaning that listeners must return (mostly in a short time frame) in order to let the execution continue normally.

==== Proxy and Tunnel

Extensions can listen to proxy/tunnel calls by defining methods in the `proxy` or `tunnel` table.

[source,lua]
----
MyExt.proxy = {}
function MyExt.proxy:getInfo()
end

-- client-side
MyExt.tunnel = {}
function MyExt.tunnel:test()
end
----

The proxy interface generated will be accessible from other resources like this:
[source,lua]
----
local my_ext = Proxy.getInterface("vRP.EXT.MyExt")
local info = my_ext.getInfo()
----

IMPORTANT: Extensions don't need and should not use proxy between them.

The tunnel is accessible (from the client-side or server-side extension) through the `remote` table.
[source,lua]
----
-- server-side
function MyExt.event:playerSpawn(user, first_spawn)
  self.remote._test(user.source)
end

-- client-side
function MyExt.event:playerDeath()
  self.remote._test()
end
----

=== Profiler

vRP embeds https://github.com/ImagicTheCat/ELProfiler[ELProfiler] to profile Lua code, vRP and resources based on it. When a resource loads `@vrp/lib/utils.lua` (which is the case for resources based on vRP), it will setup itself to be recorded by the profiler.

To use the profiler, the module must be enabled in `cfg/modules.lua`. This will keep track of created coroutines to profile them. The overhead is probably small, thus it can be enabled on a live server.

Two options are available (with permissions) in the main and admin menus to respectively profile the client-side or server-side.

NOTE: Profiling has an overhead, but mostly because of the Lua debug hook. Being a statistical/sampling profiler, profiling a long period of time is fine (low memory usage).

== General API

=== utils

`lib/utils.lua` defines some useful globals.

[source,lua]
----
-- side detection
SERVER -- boolean
CLIENT -- boolean

-- load a lua resource file as module (for a specific side)
-- rsc: resource name
-- path: lua file path without extension
module(rsc, path)

class -- Luaoop class

-- create an async returner or a thread (Citizen.CreateThreadNow)
-- func: if passed, will create a thread, otherwise will return an async returner
async(func)

-- convert Lua string to hexadecimal
tohex(str)

-- basic deep clone function (doesn't handle circular references)
clone(t)

parseInt(v)

-- will remove chars not allowed/disabled by strchars
-- allow_policy: if true, will allow all strchars, if false, will allow everything except the strchars
sanitizeString(str, strchars, allow_policy)

splitString(str, sep)
----

=== Proxy and Tunnel

==== Proxy

[source,lua]
----
-- add event handler to call interface functions 
-- name: interface name
-- itable: table containing functions
Proxy.addInterface(name, itable)

-- get a proxy interface 
-- name: interface name
-- identifier: (optional) unique string to identify this proxy interface access; if nil, will be the name of the resource
Proxy.getInterface(name, identifier)
----

==== Tunnel

[source,lua]
----
-- set the base delay between Triggers for a destination
-- dest: player source
-- delay: milliseconds (0 for instant trigger)
Tunnel.setDestDelay(dest, delay)

-- bind an interface (listen to net requests)
-- name: interface name
-- interface: table containing functions
Tunnel.bindInterface(name,interface)

-- get a tunnel interface to send requests 
-- name: interface name
-- identifier: (optional) unique string to identify this tunnel interface access; if nil, will be the name of the resource
Tunnel.getInterface(name,identifier)
----

==== Interface

* interface defined function names should not start with an underscore (`+_+`)
* the tunnel server-side call requires the player source as first parameter
* the tunnel server-side called function can use the global `source` (correct until a TriggerEvent/yield/etc) as the remote player source
* using an underscore to call a remote function interface ignores (no wait) the returned values

[source,lua]
----
-- PROXY any side, TUNNEL client-side

-- call and wait for returned values
-- ...: arguments
-- return values
interface.func(...)

-- call without waiting
-- ...: arguments
interface._func(...)

-- TUNNEL server-side

-- call and wait for returned values
-- ...: arguments
-- return values
interface.func(player, ...) -- or _func to ignore returned values
----

=== DBDriver

[source,lua]
----
-- called when the driver is initialized (connection), should return true on success
-- db_cfg: cfg/base.lua .db config
DBDriver:onInit(db_cfg)

-- should prepare the query (@param notation)
DBDriver:onPrepare(name, query)

-- should execute the prepared query
-- params: map of parameters
-- mode: 
--- "query": should return rows, affected
--- "execute": should return affected
--- "scalar": should return a scalar
DBDriver:onQuery(name, params, mode)
----

=== Extension

[source,lua]
----
self.remote -- tunnel interface to other network side

-- level: (optional) level, 0 by default
Extension:log(msg, level)

Extension:error(msg)
----

=== User

User inherits from all extensions sub-class User (if registered before the first user instantiation).

[source,lua]
----
self.source
self.name -- FiveM name (may be steam name)
self.id
self.cid -- character id
self.endpoint -- FiveM endpoint
self.data -- user data
self.cdata -- character data
self.loading_character -- flag
self.use_character_action -- action delay
self.spawns -- spawn count

-- return true if the user character is ready (loaded, not loading)
User:isReady()

User:save()

-- return characters id list
User:getCharacters()

-- return created character id or nil if failed
User:createCharacter()

-- use character
-- return true or false, err_code
-- err_code: 
--- 1: delay error, too soon
--- 2: already loading
--- 3: invalid character
User:useCharacter(id)

-- delete character
-- return true or false on failure
User:deleteCharacter(id)
----

=== vRP

==== Shared
[source,lua]
----
self.EXT -- map of name => ext
self.modules -- cfg/modules

vRP.Extension

-- register an extension
-- extension: Extension class
vRP:registerExtension(extension)

-- trigger event (with async call for each listener)
vRP:triggerEvent(name, ...)

-- trigger event and wait for all listeners to complete
vRP:triggerEventSync(name, ...)

-- msg: log message
-- suffix: (optional) category, string
-- level: (optional) level, 0 by default
vRP:log(msg, suffix, level)

-- msg: error message
-- suffix: optional category, string
vRP:error(msg, suffix)
----

.Events
[horizontal]
`extensionLoad(extension)`:: called when an extension is loaded, passing the extension instance (can be used to initialize with another extension when loaded before the latter)

==== Server

[source,lua]
----
self.cfg -- cfg/base config
self.lang -- loaded lang (https://github.com/ImagicTheCat/Luang)
self.users -- map of id => User
self.pending_users -- pending user source update (first spawn), map of ids key => user
self.users_by_source -- map of source => user
self.users_by_cid -- map of character id => user

-- db/SQL API
self.db_drivers
self.db_driver
self.db_initialized

vRP.DBDriver

-- return identification string for a specific source
vRP.getSourceIdKey(source)

vRP.getPlayerEndpoint(player)

vRP.getPlayerName(player)

-- register a DB driver
-- db_driver: DBDriver class
vRP:registerDBDriver(db_driver)

-- prepare a query
--- name: unique name for the query
--- query: SQL string with @params notation
vRP:prepare(name, query)

-- execute a query
--- name: unique name of the query
--- params: map of parameters
--- mode: default is "query"
---- "query": should return rows (list of map of parameter => value), affected
---- "execute": should return affected
---- "scalar": should return a scalar
vRP:query(name, params, mode)

-- shortcut for vRP.query with "execute"
vRP:execute(name, params)

-- shortcut for vRP.query with "scalar"
vRP:scalar(name, params)

-- user data
-- value: binary string
vRP:setUData(user_id,key,value)

vRP:getUData(user_id,key)

-- character data
-- value: binary string
vRP:setCData(character_id,key,value)

vRP:getCData(character_id,key)

-- server data
-- value: binary string
vRP:setSData(key,value,id)

vRP:getSData(key,id)

-- global data
-- value: binary string
vRP:setGData(key,value)

vRP:getGData(key)

-- reason: (optional)
vRP:kick(user, reason)

vRP:save()
----

.Events
[horizontal]
(sync) `characterLoad(user)`:: called right after the character loading
(sync) `characterUnload(user)`:: called before character unloading
`playerJoin(user)`:: called when a player joins (valid user)
`playerRejoin(user)`:: called when a player re-joins (ex: after a crash)
`playerDelay(user, state)`:: called when the player tunnel delay changes, `state` is true if delay is enabled
`playerSpawn(user, first_spawn)`:: called when the player spawns
`playerDeath(user)`:: called when the player dies
(sync) `playerLeave(user)`:: called before user removal
`save`:: called when vRP performs a save (can be used to sync the save of external extension data)

==== Client

[source, lua]
----
self.cfg -- cfg/client config
----

.Events
[horizontal]
`playerSpawn()`:: called when the player spawns
`playerDeath()`:: called when the player dies

== Modules

See link:modules/[].
