[OUTDATED] Don't use YAML.

WARNING
see https://forums.skunity.com/threads/outdated-dont-use-yaml.3850/page-2#post-57240


Unfortunately, YAML is one of the most inefficient things you can do in Skript. It may seem like a convenience -- or even a necessary enhancement -- at first glance, but the harsh reality is: no existing addon gets it right.

Why use YAML?

Before we dive deep into the dirty details of Skript’s disgracefully bad YAML support, let’s first understand why it’s used despite Skript’s built-in configurable options. Skript’s goal is to give absolutely anyone, regardless of prior programming knowledge, the ability to modify and customize their server. In fact, Skript’s level of entry is so low many people inevitably find themselves picking it up as their first programming language (whether or not that’s a good thing is a debate for another day). Those who eventually become comfortable with their scripting abilities then try to emulate regular Java plugins as much as possible, and this is where YAML comes to mind.

YAML is the configuration file format officially supported by Bukkit (and its forks, like Spigot), which makes YAML an obvious choice for plugins wanting easy-to-implement and easy-to-manage configs; a majority of plugins use ‘config.yml’ as their sole means of customization. So any script author who wants to create an accurate plugin illusion would use ‘config.yml’ too, right?

What’s wrong with YAML?

The problem with YAML in Skript is that every addon that supports it parses YAML files every single time their syntax is used. In other words, whenever you retrieve a single value from a YAML file, ALL bytes are read, ALL values are parsed, and EVERYTHING but the specific value you were looking for is immediately thrown away. This is the exact opposite of how YAML is used by actual plugins; YAML files aren’t parsed over and over again, they’re parsed once and stored in memory -- much like Skript’s own variables.

See for yourself:


All three addons behave in the same way: parse the entire file, get the value, and discard everything. Again, this is the exact, polar opposite of normal plugin behavior. It’s also terrible for performance; reading a file from disk isn’t a light operation, but then also parsing its contents every single time is icing on the cake. Using YAML in any of your scripts (in its current state, at least) is a guaranteed performance cut.

How bad is it?

I created a simple test script to demonstrate the performance of Skript variables versus YAML files. The data speaks for itself.

Generating 3000 random values, and retrieving 500 random values:


Generating 2500 random values, and retrieving 2500 random values:


The script:
#
# Variables vs. YAML Test
# -----------------------
# By: RezzedUp
# -----------------------
# Requires:
# - Skript 2.2 (functions)
# - SkUtilities (yaml & files)
#

#
# Change the values below to test different scenarios.
#
# PAUSE: How long the script should wait when it encounters its 'pause' state.
#
# *_TESTS: The number of tests to run. This value modifies the number of unique
# values generated for 'set' tests, and how many values are randomly
# selected for 'get' tests.
#
# *_PAUSE: The number of tests to run before the script should enter its 'pause' state.
# This prevents the server from crashing (because YAML is incredibly inefficient),
# but please remember that this will also increase test-time.
#

options:

PAUSE: wait 2 ticks

SET_TESTS: 2500
SET_PAUSE: 250

GET_TESTS: 2500
GET_PAUSE: 250

on script load:

delete {test::*}

on script unload:

delete {test::*}

function randomString(length: integer = 20, alphabet: string = "abcdefghijklmnopqrstuvwxyz0123456789-_") :: string:

set {_chars::*} to {_alphabet} split at ""
set {_string} to ""

while length of {_string} is less than {_length}:

set {_string} to "%{_string}%%random element out of {_chars::*}%"

return {_string}

command /test:
trigger:

delete {test::*}

send "&aSetting {@SET_TESTS} variables..."

set {_var-set-start} to now
set {_var-set-iterations} to 0

while {_var-set-iterations} is less than {@SET_TESTS}:

set {_key} to randomString()

{test::%{_key}%} isn't set

set {test::%{_key}%} to {_key}

add 1 to {_var-set-iterations}

mod({_var-set-iterations}, {@SET_PAUSE}) is 0

{@PAUSE}

send "Set %{_var-set-iterations}% variables. %difference between now and {_var-set-start}% since starting."

set {_var-set-time} to difference between now and {_var-set-start}
send "&6Variable-set test:&f %{_var-set-time}%"

if file "test.yml" exists:
delete file "test.yml"
create file "test.yml"

send "&aSetting {@SET_TESTS} YAML values..."

set {_yaml-set-start} to now
set {_yaml-set-iterations} to 0

loop {test::*}:

set yaml value loop-index from file "test.yml" to loop-value
add 1 to {_yaml-set-iterations}

mod({_yaml-set-iterations}, {@SET_PAUSE}) is 0

{@PAUSE}

send "Set %{_yaml-set-iterations}% YAML values. %difference between now and {_yaml-set-start}% since starting."

set {_yaml-set-time} to difference between now and {_yaml-set-start}
send "&6YAML-set test: &f%{_yaml-set-time}%"

#
# GET TESTS
#

send "&aTesting {@GET_TESTS} random variables"

set {_var-get-start} to now

loop {@GET_TESTS} times:

set {_var-get-key} to a random element out of {test::*}
set {_var-get-value} to {test::%{_var-get-key}%}

mod(loop-number, {@GET_PAUSE}) is 0

{@PAUSE}

send "Got %loop-number% values. %difference between now and {_var-get-start}% since starting."

set {_var-get-time} to difference between now and {_var-get-start}
send "&6Variable-get test: &f%{_var-get-time}%"

send "&aTesting {@GET_TESTS} random YAML values"

set {_yaml-get-start} to now

loop {@GET_TESTS} times:

set {_yaml-get-key} to a random element out of {test::*}
set {_yaml-get-value} to yaml value {_yaml-get-key} from file "test.yml"

mod(loop-number, {@GET_PAUSE}) is 0

{@PAUSE}

send "Got %loop-number% YAML values. %difference between now and {_yaml-get-start}% since starting."

set {_yaml-get-time} to difference between now and {_yaml-get-start}
send "&6YAML-get test: &f%{_yaml-get-time}%"

send "&f--- RESULTS: ---"
send "&bSet {@SET_TESTS} variables test:&f %{_var-set-time}%"
send "&3set {@SET_TESTS} YAML values test: &f%{_yaml-set-time}%"
send "&bGet {@GET_TESTS} variables test: &f%{_var-get-time}%"
send "&3Get {@GET_TESTS} YAML values test: &f%{_yaml-get-time}%"


What variables can do in seconds takes minutes in YAML. Also, notice how the tests must 'pause' every so often: YAML in Skript is so inefficient, this test will crash the server without taking breaks.

What should be used instead?

Use Skript’s built-in tools, like options and variables. The only legitimate use case for YAML in Skript is checking other plugins’ configs; anything else should be off limits.

More info will be added later (work in progress).