Skip to content

Executables#

Executables are the most vital part of Volt. They are used to manipulate control flow in an easy and user-friendly manner.

They are essentially just module scripts. That's it. The only difference from a normal module script is that Volt knows how to read these module scripts and execute their code in a way that makes your development workflow significantly smoother.

Executable Creation#

You can store your executables anywhere. It's recommended you create an individual folder to group your executables. Let's say you are creating a Money executable to handle player money. First you would create a folder called Money then within that folder create two module scripts. One for the server and one for the client. To help distinguish them within the explorer you should append .Server or .Client to their names. However, Volt doesn't read your executable names and this is entirely personal preference.

Tip

Stay organized! Prefer creating a folder within ReplicatedStorage called Exe or Executables to store your executables and prevent clutter.

Executable Tables#

Now that you have your client and server executables you can start writing code. There is absolutely nothing that distinguishes the executables except for the fact that one will be ran on the server and the other will be ran on the client.

Server Example

local Money = { Name = 'Money', Async = false }

function Money.OnExecute()
    print('Server!')
end

return Money

Client Example

local Money = { Name = 'Money', Async = false }

function Money.OnExecute()
    print('Client!')
end

return Money

These examples are actually identical and run on their respective sides. You may have noticed the Name and Async properties as well as the OnExecute function of the executable table. These are special properties.

Special Executable Properties#

Properties Type Description
Name string Defines the executable name. This is used so executables can communicate with each other.
Async boolean Defines whether an executable should be ran on a separate thread when executed.
OnExecute function Called when the executable is executed from its respective server or client.

Communicating Across Executables#

In this example assume we have a Money and Role executable. The Money executable will handle our game's in-game money system for each player. The Role executable will handle our game's in-game role system. Each player will get assigned a role (Civilian, Criminal, or Cop) upon joining. Their money will then be decided based on their role.

Role.Server

local Role = { Name = 'Role', Async = false }

-- This table is within the Role table so it is public and can be accessed by other executables
Role.ValidRoles = {
    'Civilian',
    'Criminal',
    'Cop'
}

-- By not including our players table in the returned Role table we've made it private
local players = {}

function Role.OnExecute()
    game:GetService('Players').PlayerAdded:Connect(function(player)
        math.randomseed(tick())
        local roleChosen = Role.ValidRoles[math.random(1, #Role.ValidRoles)]
        players[player] = roleChosen

        -- Await will wait x amount of time (5s in this example) for another executable to execute and then call a function
        Role.Volt.Server.Await('Money', 5, function()
            -- We know Money has executed so we have access to the AssignMoney function
            Role.Volt.Server.Money.AssignMoney(player, roleChosen)
        end)
    end)
end

--[[
    Function for getting the role of a player
    Can be called from other executables!
]]
function Role.GetRole(player)
    return players[player]
end

return Role

Money.Server

local Money = { Name = 'Money', Async = false }

local players = {}

function Money.OnExecute()

end

function Money.AssignMoney(player, roleChosen)
    local Role = Money.Volt.Server.Role

    if (roleChosen == Role.ValidRoles[1]) then  -- Civilian
        players[player] = 30
    elseif (roleChosen == Role.ValidRoles[2]) then -- Criminal
        players[player] = 20
    elseif (roleChosen == Role.ValidRoles[3]) then -- Cop
        players[player] = 50
    end
end

return Money

Main Server Script

local Volt = require(game.ReplicatedStorage.Volt)
local Role = Volt.import('Role.Server')
local Money = Volt.import('Money.Server')

Volt.Server.Execute({
    Role,
    Money
}, true) -- Second argument will override the async property of executables

So this is a pretty extensive example. It showcases that Volt injects itself into executables under their return tables. So you can access others by doing:

MyExecutableTable.Volt.Server.SomeOtherExecutable
MyExecutableTable.Volt.Client.SomeOtherExecutable

Running Executables#

Executables should be ran from a server or local script. Smaller games might only have one server script and one local script for each side to handle all their executables. Larger games might need more.

Server Script

local Volt = require(game.ReplicatedStorage.Volt)
local SomeExecutable = Volt.import('Executables/SomeExecutable.Server')

Volt.Server.Execute({
    SomeExecutable
})

Local Script

local Volt = require(game.ReplicatedStorage.Volt)
local SomeExecutable = Volt.import('Executables/SomeExecutable.Client')

Volt.Client.Execute({
    SomeExecutable
})