Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- extends Resource
- ##Functions similar to Rust's Option type, except I can't enforce the internal type as there is no
- ## assignment operation overriding in GDScript. Instead use the assign method.
- ##Option exists only to wrap a value. If no value is available, wrap null.
- ##Mostly useful for wrapping Vectors (and to a lesser extent Arrays) as they cannot be null.
- ##Typically Options should be created with either the None or Some static functions.
- class_name Option
- ##The internal value we're wrapping, or null.
- var _value
- ##The type of values this Option is allowed to store.
- ##This stores the script ('class') of the Object we wrap.
- var _script_type: Variant = null
- ##The same as _script_type, but for built-in types (anything in the Variant.Type enum other than Object).
- var _builtin_type: Variant.Type = TYPE_MAX
- ##The same as _builtin_type, but for built-in classes rather than types.
- ##This would be something like InputEvent, for example.
- var _class: String = ""
- ##Option exists only to wrap a value. If no value is available, wrap null.
- ##The parameters are the value we wrap and, optionally, a class to restrict this Option's value to.
- ##If this optional parameter is given, only null and values of that type can be assigned to this Option.
- ##Otherwise this Option can wrap any value.
- func _init(value_or_null: Variant, force_type: Variant = null):
- _value = value_or_null
- #No sense in trying to figure out what type this is if it's null.
- if force_type == null:
- return
- if force_type is Variant.Type:
- _builtin_type = force_type
- #If this isn't in the Variant.Type enum, are we a built-in class?
- elif force_type is String:
- _class = force_type
- #If this isn't in the Variant.Type enum, and we aren't a built-in class, are we a script class?
- elif force_type.has_method("get_global_name"):
- _script_type = force_type
- else:
- assert(false, "Encountered unexpected type at Option creation time!")
- ##Creates and returns an empty Option. If provided with a type, this Option will only allow
- ## data of the given type to be stored inside it.
- static func None(require_type = null) -> Option:
- return Option.new(null, require_type)
- ##Creates and returns an Option wrapping the provided value, asserting that the value is not null.
- ##Only values of the same type as the data we're wrapping can be assigned to this Option in the future.
- static func Some(value_not_null: Variant) -> Option:
- assert(value_not_null != null, "Attempted to create an Option by wrapping \"null\" in Some. Some cannot contain null!")
- if value_not_null is Option:
- push_warning("Wrapping type \"Option\" in an Option! " +
- "Wrapping an Option inside of another Option is technically allowed, but likely unintentional.")
- var val_type = typeof(value_not_null)
- if val_type == TYPE_OBJECT:
- if value_not_null.get_script() != null:
- val_type = value_not_null.get_script()
- else:
- val_type = value_not_null.get_class()
- return Option.new(value_not_null, val_type)
- ##Returns true if our internal value is null, false otherwise.
- func is_none() -> bool:
- return _value == null
- ##Returns true if our internal value is not null, false otherwise.
- func is_some() -> bool:
- return _value != null
- ##Only returns true if this Option is not None and the provided Callable returns true.
- ##The Callable must be able to take this Option's value as a parameter and return a boolean.
- ##For example, you could use is_some_and to check that a number A) exists and B) is even like so:
- ##var op_num = Some(16)
- ##op_num.is_some_and(func(num): return num % 2)
- ##This would return true, as the number does exist and is even.
- func is_some_and(fn: Callable) -> bool:
- if is_none():
- return false
- var result = fn.call(self.unwrap())
- assert(result is bool, "The Callable passed to is_some_and must return a bool!")
- return result
- ##Returns our internal value and asserts it is not null (a None Option).
- func unwrap():
- assert(_value != null, "Attempted to unwrap a null value.")
- return _value
- ##If our internal value is not null, return it.
- ##Otherwise, we call the provided callable and return its result.
- ##We assert that the value returned by the callable is not null.
- func unwrap_or_else(callable: Callable):
- if _value == null:
- var result = callable.call()
- assert(result != null, "Callable passed to unwrap_or returned null!")
- return result
- return _value
- ##If our internal value is not null, return it. Otherwise, return the provided default.
- ##We assert that default is not null.
- func unwrap_or_default(default):
- if _value == null:
- assert(default != null, "Default value provided to unwrap_or_default was null!")
- return default
- return _value
- ##The same as unwrap, but if we are a None Option (our internal value is null)
- ## the provided string is printed by the assert statement instead of a generic error message.
- func expect(err_msg: String) -> Variant:
- assert(_value != null, err_msg)
- return _value
- ##If our internal value is null, we return a new None.
- ##Otherwise we call the provided Callable, passing our internal value as an argument
- ## and returning the result wrapped in Some.
- ##map differs from and_then in that map is meant to be infallible, it returns None only if
- ## this Option itself is None. In other words, it can take a Callable returning any value.
- ##map exists mostly to extract information from an Option, if it exists.
- ##and_then is primarily used to perform some fallible operation on the condition a value exists.
- ##and_then only takes Callables that return either an Option or null.
- func map(callable: Callable) -> Option:
- if is_none():
- return None()
- var result = callable.call(_value)
- if result == null:
- return None()
- if result is Option:
- push_warning("Value returned by Callable provided to map() returns type Option. " +
- "This will result in an Option wrapped inside another Option. Is this intended?")
- return Some(result)
- ##If our internal value is null, we return the default value.
- ##Otherwise we call the provided Callable, passing our internal value as an argument
- ## and returning the result as long as it isn't null.
- ##If the Callable does return null, we return the default value.
- ##map_or differs from map in that it always returns a value while map returns an Option.
- ##Basically, map_or is the same as if you called map and then used unwrap_or on the result.
- func map_or(default: Variant, callable: Callable) -> Variant:
- assert(default != null, "Null cannot be passed as the default value to map_or!")
- if default is Option:
- push_warning("Default value provided to map_or is of type Option. " +
- "Would map() or and_then() work better?")
- if is_none():
- return default
- var result = callable.call(_value)
- if result != null:
- if result is Option:
- push_warning("Value returned by Callable in map_or is of type Option. " +
- "Would map() or and_then() work better?")
- return result
- return default
- ##If our internal value is null, we return a new None.
- ##Otherwise we call the provided Callable, passing our internal value as an argument
- ## and returning the result. If the result is null, we return None.
- ##and_then differs from map in that and_then is primarily used to perform operations on the
- ## condition a value exists. These operations may be fallible, and so the Callable must
- ## return an Option or null.
- ##map exists mostly to extract information from an Option, if it isn't None,
- ## and always returns the extracted information wrapped in a new Option.
- func and_then(callable: Callable) -> Option:
- if is_none():
- return None()
- var result = callable.call(_value)
- if result == null:
- return None()
- assert(result is Option, "Callable passed to and_then must return an Option! Would map_or be better?")
- return result
- ##Returns true if the provided value is the same as our internal value.
- ##Always returns false if our internal value is null.
- func matches(comp: Variant) -> bool:
- if _value == null:
- return false
- return comp == _value
- ##Assigns a new value to this Option. If not null, and we've restricted what type of data
- ## this Option can accept, we assert that the new value is the correct type.
- func assign(new_value: Variant):
- if new_value != null:
- #Make sure we have some sort of restrictions before asserting the new value matches them.
- if get_type() != null:
- assert(is_same_type(new_value), "Attempted to assign value \"" + str(new_value) +
- "\" to Option that only wraps type \"" + get_type_name() + "\".")
- _value = new_value
- ##Returns the class of data that this Option is allowed to store.
- ##If null is returned, any value is allowed.
- func get_type() -> Variant:
- if _builtin_type != TYPE_MAX:
- return _builtin_type
- if !_class.is_empty():
- return _class
- return _script_type
- ##Returns the name of the type of data this Option is allowed to store.
- ##If no type is set, the String "NO TYPE SET" is returned.
- func get_type_name() -> String:
- var result: String = "NO TYPE SET"
- if _builtin_type != TYPE_MAX:
- result = type_string(_builtin_type)
- elif _script_type != null:
- result = str(_script_type.get_global_name())
- elif !_class.is_empty():
- result = _class
- return result
- ##Returns true if this Option's value is of the specified type or inherits from it.
- ##If this Option is None, returns false.
- ##Mostly useful in asserts to guarantee types passed to functions.
- func is_type_not_none(check_type) -> bool:
- assert(check_type != null, "Passed \"null\" to is_type_not_none - this is not allowed!" +
- "A type must be provided. Did you want to use is_none()?")
- return is_instance_of(_value, check_type)
- ##Returns true if this Option's value is of the specified type or None.
- ##Mostly useful in asserts to guarantee types passed to functions.
- func is_type_or_none(check_type) -> bool:
- return _value == null || is_type_not_none(check_type)
- ##Does the given value have the same type as this Option?
- ##Always returns false if the given value is null.
- func is_same_type(check_value) -> bool:
- if check_value == null:
- return false
- if _builtin_type != TYPE_MAX:
- return is_instance_of(check_value, _builtin_type)
- if _script_type != null:
- return is_instance_of(check_value, _script_type)
- if !_class.is_empty():
- return check_value.is_class(_class)
- #return check_value.get_class().match(_class)
- #If we made it to this point... What happened!?
- assert(false, "Option is somehow both None and not None in is_same_type.")
- return false
Advertisement
Comments
-
- I made this while learning GDScript in a beta version of Godot 4.3. As such there may be more optimal solutions to some problems, either that I had overlooked or easier ways to tackle issues in newer versions. (I remember dealing with types in particular was very cumbersome.)
- I stayed as true to the Rust standard library as I could, but I did add "is_type_not_none" and "is_type_or_none" for convenience sake.
- This wound up working surprisingly well, though this is still Godot and trying to cram it into places it doesn't belong will only result in headaches. (I'm looking at you, exported Resources!)
Add Comment
Please, Sign In to add comment
Advertisement