Skip to content

Latest commit

 

History

History
232 lines (165 loc) · 7.75 KB

Data-Buckets.md

File metadata and controls

232 lines (165 loc) · 7.75 KB

What are Data Buckets?

  • Data buckets are a replacement to the well-known qglobals, but they are far more performant, reliable and simpler to use

  • You can use data buckets to store values unique to anything you would like, for example

    • NPC based flags
    • Zone based flags
    • Character based flags
    • etc.

Functions

  • Data buckets exist in these 3 main functions in both Perl and LUA
get_data(std::string bucket_key)
set_data(std::string bucket_key, std::string bucket_value, std::string expires_in)
delete_data(std::string bucket_key)

Storage

  • Data buckets are stored in the [[data_buckets]] table and has a very simple structure

    • id
    • key
    • value
    • expires
  • Expired data bucket rows will not be queryable via the quest API, they may exist in a table until 5-10 minutes have past and the server will garbage collect and wipe the table clean of expired buckets

Usage Examples

Perl

  • In this simple example you can see that we are keying by character id to set the flag to make this unique per player, we are setting it with the value of 70, and since we didn't set the optional value of unix time it never expires
if ($text =~/character-flag-test/i) {
    $key = $client->CharacterID() . "-some-epic-flag";
    quest::set_data($key, 70);
    quest::say("You have traveled far! You have a mighty (" . quest::get_data($key) . ") epic points!");
}

image

LUA

  • In this example below you can see that we set a simple global flag, we immediately access it, delete it and then try to unsuccessfully access it again because the bucket data had already been deleted
if (e.message:findi("test")) then
    e.self:Say("This is a test!")
    eq.set_data("lua_test_key", "lua_value");
    e.self:Say("We just set some bucket data with '".. eq.get_data("lua_test_key") .. "'");
    eq.delete_data("lua_test_key");
    e.self:Say("I'm going to try and access the value again... '".. eq.get_data("lua_test_key") .. "'");
end

image

Ways to Key Buckets

  • Keying is simply a way to uniquely identify a flag, if you want to make some data unique to a player, then you would need something to key uniquely to that player, such as their character_id. If you wanted to set a flag uniquely for a NPC for example, you could use the npc_type_id, for a zone you could use the zone_id. All of these circumstances are completely up to you and you have the entire Quest API to grab something that can make something unique!

Some of the examples below should give you some ideas!

By Character

$key   = $client->CharacterID() . "-some-flag";
$value = 70;
quest::set_data($key, $value);

By Door (And Zone)

sub EVENT_CLICKDOOR {
    quest::say($doorid);
    if ($doorid == 4) {
        $key   = $zoneid . '-' . $doorid . '-last-person-to-click-door";
        $value = $client->GetCleanName();

        if (quest::get_data($key)) {
            $client->Message(15, "You know... the last person to click this door was '" . quest::get_data($key) . "'");
        }

        quest::set_data($key, $value);
    }
}

Result

image

Database Result

image

By NPC

sub EVENT_DEATH_COMPLETE {
    $key = $npc->GetNPCTypeID() . "-death-count";
    quest::set_data($key, quest::get_data($key) + 1);

    $death_count = quest::get_data($key);
    quest::shout("Man! I've died (" . $death_count . ") times in my lifetime!");
}

Result

image

Database

image

Expiration Examples

  • Below in this LUA example we will count the number of times a player has talked to an NPC, first by checking if we've got a bucket set at all, if not we will set an expiration time on it. Each time we call set_data, it will not over-ride the original expiration time unless we pass a new time parameter
function event_say(e)
	if (e.message:findi("hail")) then

		-- Set unique key for the bucket
		local key = e.other:GetCleanName() .. "_times_talked";

		-- If the bucket is empty, we need to set it
		-- The first time we will set an expiration on this (86400 seconds)
		if (eq.get_data(key) == "") then
			eq.set_data(key, '1', 86400);
		end

		local times_talked = tonumber(eq.get_data(key));

		e.self:Say("You know... You've talked to me " .. times_talked .. " time(s) today, get a life will ya!");

		-- Increment times talked
		eq.set_data(key, tostring(times_talked + 1));
	end

end

Result

image

Database

image

Acceptable Time Formats

We have the ability to use time shorthands if need-be, the following are acceptable time inputs

Input Time Result
15s 15 seconds
s15 15 seconds
60m 60 minutes
7d 7 days
1y 1 year
600 600 seconds

Perl Expiration

  • To set an expiration time in Perl, very similarly to the LUA example above, you would simply call your set_data function with an expiration flag as your 3rd parameter like so
quest::set_data("my_example_flag", "some_value", 3600); # 3600 seconds = 1 hour (Expire in 1 hour)

Benchmarks

  • Below are some simple benchmarks used to calculate performance. While even these numbers could be greatly optimized yet, these are plenty good for most use cases that server operators need. If you need even faster temporary data storage within the context of a zone, I would suggest using [[Entity Variables]] as they operate purely in memory

image

sub EVENT_SAY {
    use Time::HiRes;
    my $start = [ Time::HiRes::gettimeofday() ];

    if ($text =~ /random-write/i) {
        my $iterations = 1000;
        my $key_range = 100;
        quest::debug("Testing random-write... Iterations: (" . plugin::commify($iterations) . ") Key Range: " . $key_range);
        for ($i = 0; $i < $iterations; $i++) {
            quest::set_data("key_" . int(rand($key_range)), &generate_random_string(100));
        }
    }

    if ($text =~ /sequential-write/i) {
        my $iterations = 1000;
        quest::debug("Testing sequential-write... Iterations: (" . plugin::commify($iterations) . ")");
        for ($i = 0; $i < $iterations; $i++) {
            quest::set_data("key_" . $i, &generate_random_string(100), 15);
        }
    }

    if ($text =~ /sequential-read/i) {
        my $iterations = 1000;
        quest::debug("Testing sequential-read... Iterations: (" . plugin::commify($iterations) . ")");
        for ($i = 0; $i < $iterations; $i++) {
            $data = quest::get_data("key_" . $i);
            # if ($data ne "") {
            #     quest::say("Data for $i : $data");
            # }
        }
    }

    if ($text =~ /random-read/i) {
        my $iterations = 1000;
        quest::debug("Testing random-read... Iterations: (" . plugin::commify($iterations) . ")");
        for ($i = 0; $i < $iterations; $i++) {
            $data = quest::get_data("key_" . int(rand($iterations)));
        }
    }

    my $elapsed = Time::HiRes::tv_interval($start);
    quest::debug("Operation took: " . $elapsed);
}