Author Topic: Load functions only when server.cfg has been read?  (Read 660 times)

0 Members and 1 Guest are viewing this topic.

Offline Luk | twitch.tv/doctorluk

  • Newbie
  • *
  • Posts: 40
  • Karma: 12
    • View Profile
Load functions only when server.cfg has been read?
« on: December 17, 2016, 01:19:50 PM »
Hey guys, just a quick question:

I'm going to make my Karmabet Plugin available on the Steam Workshop. For this I'd like to use ConVars, so people can use their server.cfg to configure the plugin (currently the values can be set inside a .lua).
The problem with that is that GetConVar() upon plugin load returns the default ConVar, not the one in the server.cfg. If I wait a couple seconds, GetConVar actually returns the value set in the server.cfg. This makes it difficulty for me to decide whether the owner wants to use MySQL/SQLite and the appropriate DB-Name/Username/PW etc.

I just can't get my head around loading the MySQL/SQLite part only when the config has been executed. Any ideas? The plugin in question is available here: https://github.com/doctorluk/ulx-karma-betting
« Last Edit: December 17, 2016, 03:01:26 PM by Luk | twitch.tv/doctorluk »
Host of Spielwiese der Erwachsenen, a German TTT Server for adults only.

Offline Timmy

  • Respected Community Member
  • Full Member
  • *****
  • Posts: 150
  • Karma: 122
  • Code monkey
    • View Profile
Re: Load functions only when server.cfg has been read?
« Reply #1 on: December 17, 2016, 03:09:21 PM »
You could use the Think hook for this. It does not get called until the server is fully loaded and ready to receive players.

Code: Lua
  1. local dataProvider = CreateConVar( "data_provider" )
  2.  
  3. hook.Add( "Think", "read_my_convars", function ()
  4.   -- Read ConVars
  5.   print( dataProvider:GetString() )
  6.  
  7.   -- Remove the hook: this callback should only be executed one time
  8.   hook.Remove( "Think", "read_my_convars" )
  9. end )

You might also be interested in using cvars.AddChangeCallback which can call a function when a ConVar was updated.

Code: Lua
  1. local dataProvider = CreateConVar( "data_provider" )
  2.  
  3. cvars.AddChangeCallback( "data_provider", function ( convar, oldValue, newValue )
  4.   print( "ConVar data_provider changed from " .. tostring( oldValue ) .. " to " .. tostring( newValue ) )
  5. end )

« Last Edit: December 17, 2016, 03:38:37 PM by Timmy2 »
hello@timmy.ws (PGP key) • Steam • GitHub

Offline Luk | twitch.tv/doctorluk

  • Newbie
  • *
  • Posts: 40
  • Karma: 12
    • View Profile
Re: Load functions only when server.cfg has been read?
« Reply #2 on: December 18, 2016, 09:25:40 AM »
Thank you for your reply.

Well I guess I've met my very own limit of knowledge of LUA or programming in general.

I create the following convars:
Code: Lua
  1. CreateConVar( "karmabet_savemode", "none", FCVAR_ARCHIVE, "" )
  2. CreateConVar( "karmabet_mysql_host", "none", FCVAR_ARCHIVE, "" )
  3. CreateConVar( "karmabet_mysql_dbname", "none", FCVAR_ARCHIVE, "" )
  4. CreateConVar( "karmabet_mysql_username", "none", FCVAR_ARCHIVE, "" )
  5. CreateConVar( "karmabet_mysql_pw", "none", FCVAR_ARCHIVE, "" )
  6. CreateConVar( "karmabet_mysql_port", "none", FCVAR_ARCHIVE, "" )

Then later on in my sv_karma_betting_mysql.lua I have this in the very first lines:
Code: Lua
  1. if SERVER then
  2.  
  3.         require( "mysqloo" )
  4.         local use_mysql = false
  5.        
  6.         cvars.AddChangeCallback( "karmabet_mysql_port", function ( convar, oldValue, newValue )
  7.        
  8.                 print( "ConVar karmabet_mysql_port changed from " .. tostring( oldValue ) .. " to " .. tostring( newValue ) )
  9.                
  10.                 if GetConVar( "karmabet_savemode" ):GetString() == "mysql" then
  11.                         use_mysql = true
  12.                 end
  13.          
  14.                 if not use_mysql then return end
  15.  
  16.                 karmabet_loadMySQL()
  17.                
  18.         end )
  19.        
  20.         local queue = {}
  21.         local DATABASE_HOST = ""
  22.         local DATABASE_NAME = ""
  23.         local DATABASE_USERNAME = ""
  24.         local DATABASE_PASSWORD = ""
  25.         local DATABASE_PORT = 3306
  26.         local db = ""
  27.        
  28.        
  29.         function karmabet_loadMySQL()
  30.                 DATABASE_HOST = GetConVar( "karmabet_mysql_host" ):GetString()
  31.                 DATABASE_NAME = GetConVar( "karmabet_mysql_dbname" ):GetString()
  32.                 DATABASE_USERNAME = GetConVar( "karmabet_mysql_username" ):GetString()
  33.                 DATABASE_PASSWORD = GetConVar( "karmabet_mysql_pw" ):GetString()
  34.                 DATABASE_PORT = GetConVar( "karmabet_mysql_port" ):GetInt()
  35.                
  36.                 print("karmabet_loadMySQL")
  37.                 print(GetConVar( "karmabet_mysql_host" ):GetString())
  38.                 print(GetConVar( "karmabet_mysql_dbname" ):GetString())
  39.                 print(GetConVar( "karmabet_mysql_username" ):GetString())
  40.                 print(GetConVar( "karmabet_mysql_pw" ):GetString())
  41.                 print(GetConVar( "karmabet_mysql_port" ):GetInt())
  42.                
  43.                 db = mysqloo.connect( DATABASE_HOST, DATABASE_USERNAME, DATABASE_PASSWORD, DATABASE_NAME, DATABASE_PORT )
  44.                
  45.                 function db:onConnectionFailed( err )
  46.                         print( "[Karmabet] Database connection failed: " .. err )
  47.                 end
  48.                
  49.                 function db:onConnected()
  50.                         print( "[Karmabet] Connected to MySQL." )
  51.                        
  52.                         query( "SET NAMES 'utf8';" )
  53.                         query( "CREATE TABLE IF NOT EXISTS karmabet (id int(32) unsigned NOT NULL AUTO_INCREMENT, bet_id int(32) unsigned NOT NULL, name text COLLATE utf8_unicode_ci NOT NULL, steamid text COLLATE utf8_unicode_ci NOT NULL, amount int(11) NOT NULL, date timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id)) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1;" )
  54.                        
  55.                         for k, v in pairs( queue ) do
  56.                                 query( v[ 1 ], v[ 2 ] )
  57.                         end
  58.                        
  59.                         queue = {}
  60.                 end
  61.                
  62.                 db:connect()           
  63.         end

If I'd set karmabet_mysql_port first in the server.cfg then karmabet_savemode would still be none...

To make things short:
How do I...
  • create a db-Object only when all Convars have been read (the order may be random depending on the host's config, so cvars.AddChangeCallback is unreliable)?
  • define the db-Object's callbacks only when it has been created (or otherwise they'll say "db is nil")?
Or is there an easier way to achieve what I'm trying to do rather than creating the MySQL connection object myself?
Host of Spielwiese der Erwachsenen, a German TTT Server for adults only.

Offline Timmy

  • Respected Community Member
  • Full Member
  • *****
  • Posts: 150
  • Karma: 122
  • Code monkey
    • View Profile
Re: Load functions only when server.cfg has been read?
« Reply #3 on: December 18, 2016, 03:22:08 PM »
create a db-Object only when all Convars have been read (the order may be random depending on the host's config, so cvars.AddChangeCallback is unreliable)?
In this case, cvars.AddChangeCallback is a bit inconvenient. You would have to keep track of the values that have been read and have not been read. The Think hook is more appropriate here.

Code: Lua
  1. -- Create convars
  2. local saveMode = CreateConVar( "karmabet_savemode", "none" )
  3.  
  4. hook.Add( "Think", "karmabet_mysql", function ()
  5.   -- This function runs when the server is fully loaded
  6.   -- All cvars have been read and are available within this function
  7.  
  8.   hook.Remove( "Think", "karmabet_mysql" ) -- Only run this callback once
  9.  
  10.   -- We can now make sure save mode has been set to "mysql"
  11.   if saveMode:GetString() ~= "mysql" then
  12.     return -- Stop! Save mode is not mysql...
  13.   end
  14.  
  15.   -- Create database object
  16.   local db = mysqloo.connect( ... )
  17.  
  18.   -- Define callbacks
  19.   function db:onConnected()
  20.     print( "Connected!" )
  21.   end
  22.  
  23.   db:connect()
  24. end )

define the db-Object's callbacks only when it has been created (or otherwise they'll say "db is nil")?
Define any database callbacks within the Think callback, right after you instantiate the database object (see comments in code example above) to avoid any problems.

Or is there an easier way to achieve what I'm trying to do rather than creating the MySQL connection object myself?
I'm afraid not, you are supposed to instantiate the database object yourself.
« Last Edit: December 18, 2016, 03:25:31 PM by Timmy »
hello@timmy.ws (PGP key) • Steam • GitHub

Offline Luk

  • Newbie
  • *
  • Posts: 1
  • Karma: 0
    • View Profile
Re: Load functions only when server.cfg has been read?
« Reply #4 on: December 19, 2016, 04:11:16 AM »
Thanks again, very appreciated. I've now used your given code and seem to have no luck:

Code: Lua
  1.         require( "mysqloo" )
  2.         local db
  3.         local queue = {}
  4.        
  5.         hook.Add( "Think", "karmabet_mysql", function ()
  6.                 -- This function runs when the server is fully loaded
  7.                 -- All cvars have been read and are available within this function
  8.  
  9.                 hook.Remove( "Think", "karmabet_mysql" ) -- Only run this callback once
  10.  
  11.                 -- We can now make sure save mode has been set to "mysql"
  12.                 if GetConVar( "karmabet_savemode" ):GetString() ~= "mysql" then
  13.                         return -- Stop! Save mode is not mysql...
  14.                 end
  15.                
  16.                 -- Read all ConVars from server.cfg
  17.                 local DATABASE_HOST = GetConVar( "karmabet_mysql_host" ):GetString()
  18.                 local DATABASE_NAME = GetConVar( "karmabet_mysql_dbname" ):GetString()
  19.                 local DATABASE_USERNAME = GetConVar( "karmabet_mysql_username" ):GetString()
  20.                 local DATABASE_PASSWORD = GetConVar( "karmabet_mysql_pw" ):GetString()
  21.                 local DATABASE_PORT = GetConVar( "karmabet_mysql_port" ):GetInt()
  22.                
  23.                 -- Send text to console to know we're inside here
  24.                 print("Think: karmabet_mysql")
  25.                
  26.                 -- initialize the database
  27.                 db = mysqloo.connect( DATABASE_HOST, DATABASE_USERNAME, DATABASE_PASSWORD, DATABASE_NAME, DATABASE_PORT )
  28.                
  29.                 -- Define hooks
  30.                 function db:onConnectionFailed( err )
  31.                         print( "[Karmabet] Database connection failed: " .. err )
  32.                 end
  33.                
  34.                 function db:onConnected()
  35.                         print( "[Karmabet] Connected to MySQL." )
  36.                 end
  37.                
  38.                 -- Finally connect to the database
  39.                 db:connect()
  40.                
  41.         end )

This code runs without any LUA errors. But it never says [Karmabet] Connected to MySQL.!

And this is the output when running the server when it is fully loaded:
Quote
Think: karmabet_mysql
Writing cfg/banned_user.cfg.
Connection to Steam servers successful.
   Public IP is xxx.xxx.xxx.xxx.
Assigned anonymous gameserver Steam ID [A-1:xxxxxx(xxxx)].
VAC secure mode is activated.

The functions are not called upon leaving the hook-function, are they? It seems as if they're lost and since the connection to the database takes more time than the LUA script, the callbacks are gone once a connection has been made. Additionally the "db" object is nil if any of the next functions (which are not in the given code, they're defined below the hook) use it (which - if at all - happens after the Think-hook ran), although "db" within the hook should overwrite the "local db" in the second line.
Even if my settings (ip, user, password) are wrong there should've been an error message.  :'(

EDIT: I logged in via Steam from another location and now I'm at 1 Posts? What happened?  ???
« Last Edit: December 19, 2016, 04:21:23 AM by Luk »

Offline Timmy

  • Respected Community Member
  • Full Member
  • *****
  • Posts: 150
  • Karma: 122
  • Code monkey
    • View Profile
Re: Load functions only when server.cfg has been read?
« Reply #5 on: December 19, 2016, 09:35:33 AM »
The code you posted works okay.

The functions are not being called because servers go to sleep when they don't have any players. You can prevent this from happening by adding sv_hibernate_think 1 to your server.cfg. It is not required though: the server will connect to the database as soon as a player joins anyway. :)
« Last Edit: December 19, 2016, 11:14:30 AM by Timmy »
hello@timmy.ws (PGP key) • Steam • GitHub

Offline Luk | twitch.tv/doctorluk

  • Newbie
  • *
  • Posts: 40
  • Karma: 12
    • View Profile
Re: Load functions only when server.cfg has been read?
« Reply #6 on: December 19, 2016, 11:14:08 AM »
Yeeehaaa! Thank you very much!

I was only able to work with the scripts and monitor the server's log file when I wasn't home. Now it all makes sense again  ;D ;D

EDIT: Regarding your recent EDIT: Everything works fine now. db is defined inside the whole script now since these functions that use the database are only called when players are actively playing, which implies that a connection has been made before.
EDIT2: And that EDIT of yours is gone xD
« Last Edit: December 19, 2016, 12:04:12 PM by Luk | twitch.tv/doctorluk »
Host of Spielwiese der Erwachsenen, a German TTT Server for adults only.

Offline Timmy

  • Respected Community Member
  • Full Member
  • *****
  • Posts: 150
  • Karma: 122
  • Code monkey
    • View Profile
Re: Load functions only when server.cfg has been read?
« Reply #7 on: December 19, 2016, 12:26:04 PM »
Yay, glad to hear things are working now. ;D

EDIT: Regarding your recent EDIT: Everything works fine now. db is defined inside the whole script now since these functions that use the database are only called when players are actively playing, which implies that a connection has been made before.
Great! I removed that edit after I noticed you had already replied. I figured that issue would also be solved by 'waking' the server. Didn't think you would notice. :P
« Last Edit: December 19, 2016, 12:46:31 PM by Timmy »
hello@timmy.ws (PGP key) • Steam • GitHub

Offline Luk | twitch.tv/doctorluk

  • Newbie
  • *
  • Posts: 40
  • Karma: 12
    • View Profile
Re: Load functions only when server.cfg has been read?
« Reply #8 on: December 19, 2016, 01:50:25 PM »
Okay, most of it is solved now. I have one little thing left I guess.

I'm using two .lua files. One file is called something_mysql.lua and something_sqlite.lua, both having the same database-function-names for the main karmabet function which are only being defined if "mysql" or "not mysql" is chosen. Basically just wrappers for the "save"-function of the main logic.

Both have the hook.Add() construct as you told me before to determine which one to load depending on the settings. We've also seen that print() seems to be printing out information even though the server has not ticked, yet (which is very strange honestly).

The sqlite scripts starts like this:
Code: Lua
  1.         local i = 1
  2.         local function loadSQLite( force )
  3.                 -- This function runs when the server is fully loaded
  4.                 -- All cvars have been read and are available within this function
  5.  
  6.                 hook.Remove( "Think", "karmabet_sqlite_think" ) -- Only run this callback once
  7.  
  8.                 -- We can now make sure save mode has been set to "sqlite"
  9.                 if not force and string.lower( GetConVar( "karmabet_savemode" ):GetString() ) == "mysql" then
  10.                         return -- Stop! Save mode is not sqlite...
  11.                 end
  12.  
  13.                 -- We fall back to SQLite if the savemode was messed up
  14.                 if not force and string.lower( GetConVar( "karmabet_savemode" ):GetString() ) ~= "sqlite" then
  15.                         print("[Karmabet] Misconfiguration of 'karmabet_savemode', falling back to SQLite!")
  16.                 else
  17.                         print("[Karmabet] SQLite Module has been loaded." .. i)
  18.                         i = i + 1
  19.                 end
  20.                 ...
  21.                 ...
  22.         end
  23.         hook.Add( "Think", "karmabet_sqlite_think", loadSQLite( nil ) )
  24.         hook.Add( "karmabet_loadsqlite", "karmabet_sqlite_call", loadSQLite( true ) )

For some odd reason the server now prints this during loading BEFORE being fully initialized:
Quote
[Karmabet] SQLite Module has been loaded.1
[Karmabet] SQLite Module has been loaded.2

You see how I defined i as 1 at the top, incrementing it upon printing the message? Well yeah, it gets printed twice. Why does it do that? I mean, it is only a visual problem, the plugin itself seems to be working fine after it, but just for debugging's sake... why?

Lastly it should be noted that the function loadSQLite is not called from outside (since it's local) except by the hook inside the mysql script which - BEFORE connecting to MySQL - checks the convars if they've changed from default:
Code: Lua
  1. if DATABASE_HOST == "none" or DATABASE_NAME == "none" or DATABASE_USERNAME == "none" or DATABASE_PASSWORD == "none" then
  2.                         print("[Karmabet][ERROR] Your MySQL Database Settings are missing!")
  3.                         print("[Karmabet][ERROR] Your MySQL Database Settings are missing!")
  4.                         print("[Karmabet][ERROR] Your MySQL Database Settings are missing!")
  5.                         print("[Karmabet][ERROR] Loading SQLite Module instead!")
  6.                         hook.Call( "karmabet_loadsqlite" )
  7.                         return false
  8.                 end

But then again this piece of code is only run when the server "Think"s, which it is not since sv_hibernate_think is not set to 1 and nobody's connected or connecting, so technically when using "sqlite" anyway this hook is also never run, yet it prints the message mentioned before two times.

Any ideas?
And while I'm at it... I should go to sleep now. Maybe I get a "Geistesblitz" in the morning, only to notice that I overlooked something quite basic while messing with the stuff here. Thanks again for the valuable help  :D
Host of Spielwiese der Erwachsenen, a German TTT Server for adults only.

Offline Timmy

  • Respected Community Member
  • Full Member
  • *****
  • Posts: 150
  • Karma: 122
  • Code monkey
    • View Profile
Re: Load functions only when server.cfg has been read?
« Reply #9 on: December 19, 2016, 02:22:07 PM »
The third parameter in hook.Add should be a function (not a function call)! :)

You are calling the loadSQlite function, instead of passing in the function.
Code: Lua
  1. hook.Add( "Think", "karmabet_sqlite_think", loadSQLite( nil ) )

This will cause Lua to execute loadSQlite( nil ) and use the result of that as the callback (probably nil).
Code: Lua
  1. hook.Add( "Think", "karmabet_sqlite_think", nil )

You should pass the function itself.
Code: Lua
  1. hook.Add( "Think", "karmabet_sqlite_think", loadSQLite )

Or, when you need to call a function with arguments, wrap the call in another function.
Code: Lua
  1. hook.Add( "karmabet_loadsqlite", "karmabet_sqlite_call", function () loadSQLite( true ) end )
« Last Edit: December 19, 2016, 02:30:19 PM by Timmy »
hello@timmy.ws (PGP key) • Steam • GitHub

Offline Luk | twitch.tv/doctorluk

  • Newbie
  • *
  • Posts: 40
  • Karma: 12
    • View Profile
Re: Load functions only when server.cfg has been read?
« Reply #10 on: December 20, 2016, 09:32:16 AM »
And while I'm at it... I should go to sleep now. Maybe I get a "Geistesblitz" in the morning, only to notice that I overlooked something quite basic while messing with the stuff here. Thanks again for the valuable help  :D

Looks like it was just like I anticipated.

The third parameter in hook.Add should be a function (not a function call)! :)

I've used the hook.Add function before and didn't remember to only provide a function name, not an actual function call. Now that I've changed it to
Code: Lua
  1.         hook.Add( "Think", "karmabet_sqlite_think", loadSQLite )
  2.         hook.Add( "karmabet_loadsqlite", "karmabet_sqlite_call", function() loadSQLite( true ) end )
everything seems to be working as expected! Thank you!!
Host of Spielwiese der Erwachsenen, a German TTT Server for adults only.