In the following chapters we will discuss the first half of this project; a tool designed for the creation and simulation of Cellular Automata systems. Referred to in this document as Cellar.
Cellar is a Cellular Automata simulation and creation framework. It allows researchers to use a light but powerful templating-A.P.I. to define automatons. It also comes with a graphical parameter-editor that can be used by utilizing a number of simplified-G.U.I.-widgets that it exposes. You can also write your own widgets by extending the base (i.e. blank) widget.
Our program is fully customizable using its advanced built-in template-A.P.I. driven by Lua and using LuaJIT for performance optimization. It allows the researcher to fully customize the editor to suit the needs of their research, making it an adaptive simulation-manager.
It is also cross-platform, allowing the research to use a single-codebase that is completely O.S.-agnostic. It can run on:
- Windows
- MacOS
- *nix Systems.
- iOS & Android (With decreased performance.)
- HTML5 (Web)
It offers an online-console facilitating remote debugging and control.
The G.U.I. A.P.I. is written in Lua 5.1 and interpreted with LuaJIT-5.2, which offers it, and the user (researchers), access to some Lua 5.2 features; mainly goto which is used in some parts of the program.
The following libraries were utilized to create the editor's G.U.I.:
- L2D: A C++ framework for Lua development. It was used for the following reasons:
- Cross-platform building and exporting.
- Platform agnostic path-handling.
- File-path aware Lua extensibility.
- Timing functionality.
- Multi-threading support for Lua; which was crucial for the performance of our program.
- An OpenGL 3.2 graphics and rendering layer.
- Slab: A Lua/L2D Immediate Mode G.U.I. framework; used to satisfy all of the program's U.X. requirements.
- MiddleClass: A memory-aware and performant object orientation library for Lua 5.x.
Create a new instance of SmartController.
Note instances are completely independent and many can be managed simultaneously with no issues.
instance = SmartController:initialize(headerText, config)string headerText ("Editor")[Optional] The title of the editor window.
table config (x = 0, y = 0, w = windowWidth / 8, h = windowHeight)[Optional] The transform data of the editor window.
Must be called once per-frame; as it is used internally for updating all of the controller's logic.
SmartController:update(dt)number dtThe amount of time that has passed since the last update call, in milliseconds.
An enum holding all available widgets.
SmartController.Controls = {
BASE = SccBase,
LABEL = SccLabel,
STATIC_LABEL = SccStaticLabel,
RANDOMIZE = SccRandomize,
CHECKBOX = SccCheckbox,
SLIDER = SccSlider,
BUTTON_STEPPER = SccButtonStepper,
CHECKBOX_LIST = SccCheckboxList,
ACTION_BUTTON = SccActionButton,
}An enum holding all available widgets.
The main method used to add new controls to the editor.
SmartController:addControl(varName, control, config, target, updateFunc)string varNameThey key of the variable to edit; must reside within target and is passed into updateFunc. If config.title is not provided; this will also be used as the label of the control.
SccBase controlSccBase or any class extending it. The GUI widget to add. Any of the values of the Controls enum are valid for this, but custom classes may also be provided.
table config ({})A table passed into control. Can contain arbitrary data which is then used by the widget. Widgets may require certain values to be passed; however all the default widgets do not require any values.
All widgets accept a string title field which is used as their label. Other possible configs are described in each widget's section.
table target (instance:getTarget())The target of this control. If none is provided, the global target set by instance.setTarget is used. If no global target is set either, an error is thrown.
function(varName, newVal, oldVal, target) updateFunc (instance:getUpdateFunc())The update-function used by this control to modify varName inside target. If none is provided, the global update-function set by instance.setUpdateFunc is used. If no global update-function is set either, an error is thrown.
Sets the global target of newly added controls, if none is passed during their creation. Note that the target is cached and thus only effects controls added after its called.
SmartController:setTarget(target)table targetThe target passed into update-function calls for controls.
Sets the global update-function for newly added controls, if none is passed during their creation. Note that the update-function is cached and thus only effects controls added after its called.
SmartController:setUpdateFunc(func)function(varName, newVal, oldVal, target) funcThe update-function used for calls.
The base widget inherited by all other widgets. It used for the following reasons:
- To assert that the control passed into
addControlis valid. - To minimize the amount of boilerplate needed when writing widgets.
- To prepare all common widget state.
instance = SccBase:initialize(parent, target, updateFunc, varName, config)SmartController parentA reference to the SmartController object that created this widget.
table targetThe target that this control will modify.
function(varName, newVal, oldVal, target) updateFuncThe update-function called upon the widget issuing any change. This must modify the target and set target[varName] to newVal; otherwise no change will occur!
The function is free to perform any other needed changes .
string varNameThe key of the value to modify inside target.
table config ({title=varName})An optional table to pass configuration values into the widget. For all default widgets; title is used for their display-labels and, alongside all other values, it is optional, making config an optional field for them. Child widgets may override this behavior.
Provides a textual-label that displays either title or varName alongside the value of the variable being targeted by this widget - which is updated dynamically.
instance = SccLabel:initialize(...) <extends SccBase>Provides a textual- C-style formatted-label that displays either title or varName. This is intend for displaying this such as help, legends, etc...
instance = SccStaticLabel:initialize(...) <extends SccBase>Provides a button that generates a pseudo-randomized number in between config.min and config.max (inclusive) and passes it into update-function. If min and max are within the range [0, 1] then a float is produced, otherwise, an integer is produced. The produced number, in both cases, is 32-bit and it uses C's math.random for its operation.
instance = SccRandomize:initialize(...) <extends SccBase>number min (0)The lower bound of used for the R.N.G. Inclusive.
number max (1)The upper bound of used for the R.N.G. Inclusive.
Provides a simple boolean checkbox. Its value is both, passed into and obtained from target.
instance = SccCheckbox:initialize(...) <extends SccBase>number count (#self:_getVal())The number of checkboxes to display.
Provides a list of boolean checkboxes. Its value is both, passed into and obtained from target. Useful for settings flags, tabulated toggleables, etc...
instance = SccCheckboxList:initialize(...) <extends SccBase>Provides numerical slider that can either controlled with the mouse, or have a value directly inputted into it via the keyboard, for higher precision. Its value is both, passed into and obtained from target.
instance = SccSlider:initialize(...) <extends SccBase>number min (0)The lower bound allowed by the slider.
number max (1)The upper bound allowed by the slider.
number step (max * 0.01)The step size of the slider.
Similar to SccSlider but provides buttons for incrementing and decrementing the targeted value instead of a slider -- which provides greater precision. The number of buttons and their values can be customized with config.
instance = SccButtonStepper:initialize(...) <extends SccBase>array steps ({1})The number and size of increment / decrement steps. The number of buttons created by this widget is #steps * 2 and the value of each button corresponds to steps[i] and -steps[i] where i is the index of that button (starting at 1, as per Lua standards.)
number min (-math.huge)The lower bound of values allowed by the stepper.
number max (math.huge)The upper bound of values allowed by the stepper.
A blank-button that can be assigned any action the user wishes by initializing it with a custom callback.
instance = SccActionButton:initialize(...) <extends SccBase>function(target) fThe callback to be called when the button is pressed.
A powerful templating-A.P.I. intended for the creation of cellular automata in a performant, multi-threaded environment. Aims to help the user by providing a framework that can cover the needs of most cellular automata simulations.
Below is the static parts of environment in which user-written scripts are run -- a list of what is exposed to the simulation creator.
local AUTOMATA_ENV = {
--Lua metadata.
_VERSION = _VERSION,
--Basics
print = print,
type = type,
tonumber = tonumber,
tostring = tostring,
--Environment
setfenv = setfenv,
getfenv = getfenv,
--Lua control and loops.
pairs = pairs,
ipairs = ipairs,
next = next,
select = select,
unpack = unpack,
--Lua error handling.
assert = assert,
error = error,
pcall = pcall,
xpcall = xpcall,
--Lua metatables.
rawset = rawset,
rawget = rawget,
rawequal = rawequal,
setmetatable = setmetatable,
getmetatable = getmetatable,
--Lua core libs.
table = table,
math = math,
string = string,
os = _SANDBOXED_OS, --Only includes time-related functions!
bit = bit,
io = _SANDBOXED_IO,
--L2D
l2d = l2d
--Libs.
checker = checker,
csv = csv,
inspect = inspect,
class = middlecclass,
--Automata-related.
simUtils = simUtils,
}Native Lua functionality will not be explained. However, below, is a quick library of all 3rd-party tools available.
- L2D: See the "G.U.I. / Tools Used" section.
- MiddleClass: See the "G.U.I. / Tools Used" section.
- CSV: A pure-Lua
.csvparser with support for headers and custom-formats. This was slightly modified to usel2d.filesystem.openinstead ofio.opento make it platform-agnostic. - inspect: A human friendly (debugging-oriented) Lua-table printer.
Of the tools used, the following were custom-written for this project:
- Checker: An assertion library intended for unit-testing and input-validation.
- PreMade: A collection of functionality that is commonly used in cellular automata simulations -- to lighten the load of the user, and accelerate development time of simulations.
Below is the full A.P.I. of the PreMade library.
Pre-Made uses the following prefixes for its functions.
| Prefix | Usage |
|---|---|
| a | Querying functions. |
| i | Iterators. |
| m | Generators. |
| ma | Full-map generators. |
| g | G.U.I. control. |
| ga | Grouped G.U.I. controls. |
| h | Helpers and utility functions. |
Given a cell-coordinate, returns its neighbors ("neighbors" are defined as all cells in a 3x3 grid centered on the target cell).
adj, adjCounted = premade.aHex(states, grid, x, y)table statesA table containing all of the states to be accounted by adjCount. This is usually the same as rules.states but the user my pass in any table they wish -- present entries will be counted above zero, non present ones will still be present but with a count of zero.
The comparison is as follows: key == grid[x][y].name
array gridThe grid to check - must be a 2-dimensional array. If any of [x - 1, x + 1] or [y - 1, y + 1] are out-of-bounds, an error is thrown unless grid handles it gracefully.
It will be accessed as follows: state = grid[x][y]
number x
number yThe coordinates of the cell to check around.
array adjAn array of its neighbors. Contains the actual instances of each number. Order is non-deterministic.
table adjCountedA table of the counts of each state in the simulation (See states above.) neighboring the target cell. Contains a key for each state, indexed by its string name. The value is a number in the range [0, 8]. Note that an entry is present for each state defined by states, even those not neighboring the target cell.
Returns an iterator that traverses grid one row at a time, left-to-right, starting at the top.
iter = premade.iLeftRightDown(grid)array gridThe grid to traverse. Must be a 2-dimensionl array.
(x, y, state = function()) iterA stateless iterator coroutine wrapped behind a coroutine.wrap(f) function. Each call returns three values;
xandy: The coordinates of the cell.state: The instance of whichever state is in that cell.
Once iteration has finished, it returns nil.
Can be directly used in Lua's generic-loops.
Example:
for x, y, state in premade.iLeftRightDown(grid) do
print(string.format("Cell at (%d, %d): %s", x, y, state))
end
print("Done!")Returns the current working directory - relative to the premade
PATH = premade.hGetCwd()string PATHThe C.W.D. of premade.
Decodes the state (based on your simulation's rules) of a grid-cell from a PixelData object and a
state = premade.hDecodePixel(px, legend)table{number, number, number, number(nil)} pxThe pixel to decode
table legendA table of PixelData indexed by Lua-variable-valid strings, used to decode the pixels.
(string or nil) stateThe name of the state, if one is found, nil otherwise.
Given a table of weights, randomly returns one of the values in that table -- respecting their weights. This function is memoized; it caches the weighted-tables it creates when call. The relationship between weights is linear.
v = premade.hWeightedRandom(t, forceUpdate)table tA table of weights, from which to randomly return a value.
boolean forceUpdate (false)If true, forces the function to discard its cache for this table and recreate it.
type(k) vThe randomly selected value. Its type is whatever the type of the key corresponding to it is.
Takes in any UTF-8 compliant string, pads it on the left side, and returns it.
paddedStr = premade.hLeftPad(str, len, pad)string strThe string to pad.
number lenThe string-length to stop padding at.
string pad (" ")The character to pad with. If #pad ~= 1 an error is thrown. Defaults to the empty-space character.
string paddedStrThe padded string. If #str >= len then paddedStr == str is guaranteed.
Identical to premade.hLeftPad but pads on the right-side instead.
paddedStr = premade.hRightPad(str, len, pad)Generates a 2-dimensional grid, given a .png or .jpg image. Intended to be used as a pre-made value for rules.generateAll
grrd = premade.maFromImage(rules, set, new)table rulesA simulations rules table.
Technically, any table may be passed, so long as it contains the following fields:
string path: A filename pointing to a valid.pngor.jpgimage. Is relative tosrc/.table legend: A table conforming to the rules ofpremade.hDecodePixel's second argument;legend.string outOfBoundsState: A string pointing to a valid state defined in the current simulation.
function(x, y, state) setA function that takes in a 2-dimensional coordinate and a state instance; and places it into the grid at that location.
This is usually passed in by Cellar itself, as this function is intended to be used as a pre-made value for rules.generateAll.
function(stateName) newA function that takes a valid state name, and returns an instance of that state.
This is usually passed in by Cellar itself, as this function is intended to be used as a pre-made value for rules.generateAll.
using the database harvested from collage campus regarding our spicified virus we processed and configured it into rules to be the base logic of how each cells interact within the simulation.
<cellar-gui.png> Above is an image showcasing all of the controls used in our COVID19-demo.
| Button Name | Usage |
|---|---|
| Screenshot | Takes a screenshot of the entire window and saves it at your home directory1 |
| seed | The seed used by all RNG operation. |
| Randomize | Randomizes the simulation and steps back up-to the current generationCount. |
| generationCount | How many generations have been simulated up to this point. |
| Step | Simulate a single generation. |
| BackStep | Undo a single step of the simulation. |
| Reset | Resets the generation count back to zero. |
| AutoIterate | Enable automatic stepping. |
| AutoIterateFrequency | Time between auto-steps in seconds. |
| Cycle Map | Cycle between all loaded maps. |
| Echo Stats | Compute stats for current generation, and echoes to console.2 |