Check out our latest project ✨ OpenChapter.io: free ebooks the way its meant to be 📖

Godot Optional

An asset by Tienne_k
The page banner background of a mountain and forest
Godot Optional thumbnail image
Godot Optional thumbnail image
Godot Optional thumbnail image
Godot Optional hero image

Quick Information

0 ratings
Godot Optional icon image
Tienne_k
Godot Optional

Introduces to Godot types inspired by the Rust programming language.- Option explicitly denotes that a value can be a null and must be handled- Result explicitly denotes that an operation can fail and must be handled- Custom Errors that carry with them details about the exception, including a cause and display message! It also acts as a place to have a centralized list of errors specific to your application, as GlobalScope.Error doesn't cover most cases.- TimedVars that keep track of when they were created, and can delete themselves after a set amount of time.- Enum structs (experimental): Enums that aren't just integers, but also carry user defined data with them! Good for state handling!Examples, documentation, and playground (see examples/misc) also included!PRs are welcome!!

Supported Engine Version
4.2
Version String
3.0
License Version
MIT
Support Level
community
Modified Date
1 year ago
Git URL
Issue URL

godot-optional

Better error handling for Godot!

Introduces to Godot Option, Result, and custom Error types inspired by Rust

Features

  • Optionals to explicitly annotate that a variable can be null
  • Results to explicitly annotate that an operation can fail
  • Custom error types specific to your application
  • TimedVars that keep track of how long they've existed for, and can delete themselves after some time
  • EnumStructs (Experimental)

Option

A generic Option<T>

Options are types that explicitly annotate that a value can be null, and forces the user to handle the exception

Basic usage:

# By returning an Option, it's clear that this function can return null, which must be handled
func get_player_stats(id: String) -> Option:
    return Option.None() # Represents a null
    return Option.Some( data ) # Sucess!

var res: Option = get_player_stats("player_3")
if res.is_none():
    print("Player doesn't exist!")
    return

# Getting the contained value (in order of safety):
var data = res.unwrap_or( 42 ) # Get from default value
var data = res.unwrap_or_else( some_complex_function ) # Get default value from function
var data = res.expect("Res was None!")
var data = res.unwrap() # Crashes if None, but quick for prototyping
var data = res.unwrap_unchecked() # Least safe. It's okay to use it here because we've already checked above

Option also comes with a safe way to index arrays and dictionaries

var my_arr = [2, 4, 6]
print( Option.arr_get(1) )  # Prints "Some(4)"
print( Option.arr_get(4) )  # Prints "None" because index 4 is out of bounds

README

Result

A generic Result<T, E>

Results are types that explicitly annotate that an operation (most often a function call) can fail, and forces the user to handle the exception

In case of a success, the Ok variant is returned containing the value returned by said operation In case of a failure, the Err variant is returned containing information about the error.

Basic usage:


# By returning a Result, it's clear that this function can fail
func my_function() -> Result:
    return Result.from_gderr(ERR_PRINTER_ON_FIRE)
    # Also supports custom error types!
    return Result.Err( Error.new(Error.MyCustomError).info("expected", "some_value") )
    return Result.Err("my error message")
    return Result.Ok(data) # Success!

var res: Result = my_function()
# Ways to handle results:
if res.is_err():
    res.stringify_error() # @GlobalScope.Error to String
    # Custom errors can bear extra details. See the "Custom error types" section below
    res.err_cause(...) .err_info(...) .err_msg(...)\
        .report()
    return

# Getting the contained value (in order of safety):
var data = res.unwrap_or( 42 ) # Get from default value
var data = res.unwrap_or_else( some_complex_function ) # Get default value from function
var data = res.expect("my_function failed!")
var data = res.unwrap() # Crashes if res is Err. Least safe, but quick for prototyping
var data = res.unwrap_unchecked() # It's okay to use it here because we've already checked above

print(res) # "Ok( whatever data is contained )"

Result also comes with a safe way to open files and parse JSON

# "Error" refers to custom error types. Not to be confused with @GlobalScope.Error
 var res: Result = Result.open_file("res://file.txt", FileAccess.READ) # Result<FileAccess, Error>
 var json_res: Result = Result.parse_json_file("res://data.json") # Result<data, Error>

README

Custom error types

Godot-optional introduces a custom Error class for custom error types.

The aim is to allow for errors to carry with them details about the exception, leading to better error handling. It also acts as a place to have a centralized list of errors specific to your application, as Godot's global Error enum doesn't cover most cases.

Usage:

# Can be made from a Godot error, and with optional additional details
var myerr: Error = Error.new(ERR_PRINTER_ON_FIRE) .cause('Not enough ink!')
    # Or with an additional message too!
    .msg("The printer gods demand input..")

# Prints: "Printer on fire { "cause": "Not enough ink!", "msg": "The printer gods demand input.." }"
print(myerr)
# Push the error to the debugger
myerr.report()

# You can even nest them!
# (These two lines do the same thing)
Error.from_gderr(ERR_TIMEOUT)\
    .cause( Error.new(Error.Other).msg("Oh no!") )
Error.new(Error.Other).msg("Oh no!")\
    .as_cause( Error.from_gderr(ERR_TIMEOUT) )

# Used alongside a Result:
Result.error(Error.MyCustomError)
Result.open_file( ... ) .err_msg("Failed to open the specified file")

You can also define custom error types specific to your application in the Error script

# res://addons/optional/Error.gd
enum {
    Other,
    # Define custom errors here ...
    MyCustomError,
}

README

TimedVar

TimedVars are variables that keep track of when they were created and can expire after a certain amount of time if configured to.

When expired, the contained value will be deleted (set to null / None)

Example: Creating a combo system using TimedVars

var combo: TimedVar = TimedVar.empty() # Combo not yet started
print("  combo = ", combo) # "TimedVar(<null>: alive for 0.00s)"

# Player input ...

# Start the combo with a slash
print("SLASH!")
combo = TimedVar.with_lifespan("slash", 1000)
# Same as writing one of the following:
combo = TimedVar.new("slash") .set_lifespan(1000)
combo.set_value("slash") .set_lifespan(1000) # 1s window for following combos

# Now, combo = TimedVar(slash: expires in 1.00s)
# Player input ...

# Follow it up with a big slash
if combo.get_value() .matches("slash"):
    print("BIG SLASH!")
    combo.set_value("slash_big") # Also resets lifespan back to that 1s window we defined earlier
else:
    # Too late! `combo` already expired, so no more follow-ups!
    print("No big slash")

# Now, combo = TimedVar(slash_big: expires in 1.00s)
# Player input ...

# End it with a 'biggest slash', but with a tighter timing window of 0.5s
# Using take() (or in this case, take_timed()) takes care of finishing the
#  combo with no loose ends
if combo.take_timed(500) .matches("slash_big"):
    print("BIGGEST SLASH ULTIMATE!!!")
else:
    # Too late! `combo` already expired!
    print("No biggest slash :(")

# Now, combo = TimedVar::Expired

Enum Structs (experimental)

Usage:

# Declare enum
static var AnimalState: EnumStruct = EnumStruct.new()\
    .add(&"Alive", { "is_hungry" : false })\
    .add(&"Dead") # A dead animal can't be hungry

# There are a couple ways to get an EnumStruct variant:
var cat_state: EnumVariant = AnimalState.Alive
cat_state.is_hungry = true
# or
var cat_state: EnumVariant = AnimalState.variant(&"Alive", { "is_hungry" : true })

print(cat_state) # Prints: Alive { "is_hungry" : true }

Notice how EnumStructs and EnumVariants can both be treated like normal objects, but with the user declared properties.

Note: There are also EnumDicts which use Dictionaries as variants instead of EnumVariant

The above code is the same as doing the following in Rust:

enum AnimalState {
    Alive{ is_hungry: bool },
    Dead,
}

let cat_state: AnimalState = AnimalState::Alive{ is_hungry: true };

Introduces to Godot types inspired by the Rust programming language.
- Option explicitly denotes that a value can be a null and must be handled

- Result explicitly denotes that an operation can fail and must be handled

- Custom Errors that carry with them details about the exception, including a cause and display message! It also acts as a place to have a centralized list of errors specific to your application, as GlobalScope.Error doesn't cover most cases.

- TimedVars that keep track of when they were created, and can delete themselves after a set amount of time.

- Enum structs (experimental): Enums that aren't just integers, but also carry user defined data with them! Good for state handling!

Examples, documentation, and playground (see examples/misc) also included!
PRs are welcome!!

Reviews

0 ratings

Your Rating

Headline must be at least 3 characters but not more than 50
Review must be at least 5 characters but not more than 500
Please sign in to add a review

Quick Information

0 ratings
Godot Optional icon image
Tienne_k
Godot Optional

Introduces to Godot types inspired by the Rust programming language.- Option explicitly denotes that a value can be a null and must be handled- Result explicitly denotes that an operation can fail and must be handled- Custom Errors that carry with them details about the exception, including a cause and display message! It also acts as a place to have a centralized list of errors specific to your application, as GlobalScope.Error doesn't cover most cases.- TimedVars that keep track of when they were created, and can delete themselves after a set amount of time.- Enum structs (experimental): Enums that aren't just integers, but also carry user defined data with them! Good for state handling!Examples, documentation, and playground (see examples/misc) also included!PRs are welcome!!

Supported Engine Version
4.2
Version String
3.0
License Version
MIT
Support Level
community
Modified Date
1 year ago
Git URL
Issue URL

Open Source

Released under the AGPLv3 license

Plug and Play

Browse assets directly from Godot

Community Driven

Created by developers for developers