Gate WASM target.
This commit is contained in:
parent
b6e1f652b6
commit
7f4f737ff2
36
README.md
36
README.md
@ -11,6 +11,11 @@ Rhai - Embedded Scripting for Rust
|
|||||||
Rhai is an embedded scripting language and evaluation engine for Rust that gives a safe and easy way
|
Rhai is an embedded scripting language and evaluation engine for Rust that gives a safe and easy way
|
||||||
to add scripting to any application.
|
to add scripting to any application.
|
||||||
|
|
||||||
|
Supported targets
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
* All common targets, including [WASM] and `no-std`.
|
||||||
|
|
||||||
Features
|
Features
|
||||||
--------
|
--------
|
||||||
|
|
||||||
@ -27,13 +32,11 @@ Features
|
|||||||
* Sand-boxed - the scripting [`Engine`], if declared immutable, cannot mutate the containing environment unless explicitly permitted (e.g. via a `RefCell`).
|
* Sand-boxed - the scripting [`Engine`], if declared immutable, cannot mutate the containing environment unless explicitly permitted (e.g. via a `RefCell`).
|
||||||
* Rugged - protection against malicious attacks (such as [stack-overflow](#maximum-call-stack-depth), [over-sized data](#maximum-length-of-strings), and [runaway scripts](#maximum-number-of-operations) etc.) that may come from untrusted third-party user-land scripts.
|
* Rugged - protection against malicious attacks (such as [stack-overflow](#maximum-call-stack-depth), [over-sized data](#maximum-length-of-strings), and [runaway scripts](#maximum-number-of-operations) etc.) that may come from untrusted third-party user-land scripts.
|
||||||
* Track script evaluation [progress](#tracking-progress-and-force-terminate-script-run) and manually terminate a script run.
|
* Track script evaluation [progress](#tracking-progress-and-force-terminate-script-run) and manually terminate a script run.
|
||||||
* [`no-std`](#optional-features) support.
|
|
||||||
* Supports compiling to `WASM`, optionally with [minimal builds](#minimal-builds).
|
|
||||||
* [Function overloading](#function-overloading).
|
* [Function overloading](#function-overloading).
|
||||||
* [Operator overloading](#operator-overloading).
|
* [Operator overloading](#operator-overloading).
|
||||||
* Organize code base with dynamically-loadable [modules].
|
* Organize code base with dynamically-loadable [modules].
|
||||||
* Scripts are [optimized](#script-optimization) (useful for template-based machine-generated scripts) for repeated evaluations.
|
* Scripts are [optimized](#script-optimization) (useful for template-based machine-generated scripts) for repeated evaluations.
|
||||||
* Support for [minimal builds](#minimal-builds) by excluding unneeded language [features](#optional-features).
|
* Support for [minimal builds] by excluding unneeded language [features](#optional-features).
|
||||||
* Very few additional dependencies (right now only [`num-traits`](https://crates.io/crates/num-traits/)
|
* Very few additional dependencies (right now only [`num-traits`](https://crates.io/crates/num-traits/)
|
||||||
to do checked arithmetic operations); for [`no-std`](#optional-features) builds, a number of additional dependencies are
|
to do checked arithmetic operations); for [`no-std`](#optional-features) builds, a number of additional dependencies are
|
||||||
pulled in to provide for functionalities that used to be in `std`.
|
pulled in to provide for functionalities that used to be in `std`.
|
||||||
@ -142,8 +145,10 @@ Making [`Dynamic`] small helps performance due to better cache efficiency.
|
|||||||
|
|
||||||
### Minimal builds
|
### Minimal builds
|
||||||
|
|
||||||
|
[minimal builds]: #minimal-builds
|
||||||
|
|
||||||
In order to compile a _minimal_build - i.e. a build optimized for size - perhaps for `no-std` embedded targets or for
|
In order to compile a _minimal_build - i.e. a build optimized for size - perhaps for `no-std` embedded targets or for
|
||||||
compiling to `WASM`, it is essential that the correct linker flags are used in `cargo.toml`:
|
compiling to [WASM], it is essential that the correct linker flags are used in `cargo.toml`:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[profile.release]
|
[profile.release]
|
||||||
@ -167,8 +172,19 @@ A _raw_ engine supports, out of the box, only a very [restricted set](#built-in-
|
|||||||
Selectively include other necessary functionalities by loading specific [packages] to minimize the footprint.
|
Selectively include other necessary functionalities by loading specific [packages] to minimize the footprint.
|
||||||
Packages are sharable (even across threads via the [`sync`] feature), so they only have to be created once.
|
Packages are sharable (even across threads via the [`sync`] feature), so they only have to be created once.
|
||||||
|
|
||||||
Related
|
### Compiling to WebAssembly (WASM)
|
||||||
-------
|
|
||||||
|
[WASM]: #compiling-to-WebAssembly-wasm
|
||||||
|
|
||||||
|
It is possible to use Rhai when compiling to WebAssembly (WASM), but certain features will not be available,
|
||||||
|
such as the script file API's and loading modules from external script files.
|
||||||
|
|
||||||
|
Also look into [minimal builds] to reduce generated WASM size. As of this version, a typical, full-featured
|
||||||
|
Rhai scripting engine compiles to a single WASM file around 200KB gzipped. When excluding features that are
|
||||||
|
marginal in WASM environment, the gzipped payload can be further shrunk to 160KB.
|
||||||
|
|
||||||
|
Related Resources
|
||||||
|
-----------------
|
||||||
|
|
||||||
Other cool projects to check out:
|
Other cool projects to check out:
|
||||||
|
|
||||||
@ -2453,10 +2469,10 @@ which simply loads a script file based on the path (with `.rhai` extension attac
|
|||||||
|
|
||||||
Built-in module resolvers are grouped under the `rhai::module_resolvers` module namespace.
|
Built-in module resolvers are grouped under the `rhai::module_resolvers` module namespace.
|
||||||
|
|
||||||
| Module Resolver | Description |
|
| Module Resolver | Description |
|
||||||
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `FileModuleResolver` | The default module resolution service, not available under [`no_std`]. Loads a script file (based off the current directory) with `.rhai` extension.<br/>The base directory can be changed via the `FileModuleResolver::new_with_path()` constructor function.<br/>`FileModuleResolver::create_module()` loads a script file and returns a module. |
|
| `FileModuleResolver` | The default module resolution service, not available under [`no_std`] or [WASM] builds. Loads a script file (based off the current directory) with `.rhai` extension.<br/>The base directory can be changed via the `FileModuleResolver::new_with_path()` constructor function.<br/>`FileModuleResolver::create_module()` loads a script file and returns a module. |
|
||||||
| `StaticModuleResolver` | Loads modules that are statically added. This can be used under [`no_std`]. |
|
| `StaticModuleResolver` | Loads modules that are statically added. This can be used under [`no_std`]. |
|
||||||
|
|
||||||
An [`Engine`]'s module resolver is set via a call to `Engine::set_module_resolver`:
|
An [`Engine`]'s module resolver is set via a call to `Engine::set_module_resolver`:
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ Version 0.15.1
|
|||||||
==============
|
==============
|
||||||
|
|
||||||
This is a minor release which enables updating indexers (via registered indexer setters) and supports functions
|
This is a minor release which enables updating indexers (via registered indexer setters) and supports functions
|
||||||
with `&str` parameters (maps transparently to `ImmutableString`).
|
with `&str` parameters (maps transparently to `ImmutableString`). WASM is also a tested target.
|
||||||
|
|
||||||
Buf fix
|
Buf fix
|
||||||
-------
|
-------
|
||||||
@ -28,7 +28,7 @@ New features
|
|||||||
* `Engine:register_fn` and `Engine:register_result_fn` accepts functions that take parameters of type `&str` (immutable string slice), which maps directly to `ImmutableString`. This is to avoid needing wrappers for functions taking string parameters.
|
* `Engine:register_fn` and `Engine:register_result_fn` accepts functions that take parameters of type `&str` (immutable string slice), which maps directly to `ImmutableString`. This is to avoid needing wrappers for functions taking string parameters.
|
||||||
* Set maximum limit on data sizes: `Engine::set_max_string_size`, `Engine::set_max_array_size` and `Engine::set_max_map_size`.
|
* Set maximum limit on data sizes: `Engine::set_max_string_size`, `Engine::set_max_array_size` and `Engine::set_max_map_size`.
|
||||||
* Supports trailing commas on array literals, object map literals, function definitions and function calls.
|
* Supports trailing commas on array literals, object map literals, function definitions and function calls.
|
||||||
* Supports compiling to `WASM`.
|
* Enhances support for compiling to WASM.
|
||||||
|
|
||||||
Version 0.15.0
|
Version 0.15.0
|
||||||
==============
|
==============
|
||||||
|
14
src/api.rs
14
src/api.rs
@ -555,6 +555,8 @@ impl Engine {
|
|||||||
|
|
||||||
/// Read the contents of a file into a string.
|
/// Read the contents of a file into a string.
|
||||||
#[cfg(not(feature = "no_std"))]
|
#[cfg(not(feature = "no_std"))]
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
#[cfg(not(target_arch = "wasm64"))]
|
||||||
fn read_file(path: PathBuf) -> Result<String, Box<EvalAltResult>> {
|
fn read_file(path: PathBuf) -> Result<String, Box<EvalAltResult>> {
|
||||||
let mut f = File::open(path.clone()).map_err(|err| {
|
let mut f = File::open(path.clone()).map_err(|err| {
|
||||||
Box::new(EvalAltResult::ErrorReadingScriptFile(
|
Box::new(EvalAltResult::ErrorReadingScriptFile(
|
||||||
@ -598,6 +600,8 @@ impl Engine {
|
|||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
#[cfg(not(feature = "no_std"))]
|
#[cfg(not(feature = "no_std"))]
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
#[cfg(not(target_arch = "wasm64"))]
|
||||||
pub fn compile_file(&self, path: PathBuf) -> Result<AST, Box<EvalAltResult>> {
|
pub fn compile_file(&self, path: PathBuf) -> Result<AST, Box<EvalAltResult>> {
|
||||||
self.compile_file_with_scope(&Scope::new(), path)
|
self.compile_file_with_scope(&Scope::new(), path)
|
||||||
}
|
}
|
||||||
@ -634,6 +638,8 @@ impl Engine {
|
|||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
#[cfg(not(feature = "no_std"))]
|
#[cfg(not(feature = "no_std"))]
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
#[cfg(not(target_arch = "wasm64"))]
|
||||||
pub fn compile_file_with_scope(
|
pub fn compile_file_with_scope(
|
||||||
&self,
|
&self,
|
||||||
scope: &Scope,
|
scope: &Scope,
|
||||||
@ -775,6 +781,8 @@ impl Engine {
|
|||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
#[cfg(not(feature = "no_std"))]
|
#[cfg(not(feature = "no_std"))]
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
#[cfg(not(target_arch = "wasm64"))]
|
||||||
pub fn eval_file<T: Variant + Clone>(&self, path: PathBuf) -> Result<T, Box<EvalAltResult>> {
|
pub fn eval_file<T: Variant + Clone>(&self, path: PathBuf) -> Result<T, Box<EvalAltResult>> {
|
||||||
Self::read_file(path).and_then(|contents| self.eval::<T>(&contents))
|
Self::read_file(path).and_then(|contents| self.eval::<T>(&contents))
|
||||||
}
|
}
|
||||||
@ -799,6 +807,8 @@ impl Engine {
|
|||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
#[cfg(not(feature = "no_std"))]
|
#[cfg(not(feature = "no_std"))]
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
#[cfg(not(target_arch = "wasm64"))]
|
||||||
pub fn eval_file_with_scope<T: Variant + Clone>(
|
pub fn eval_file_with_scope<T: Variant + Clone>(
|
||||||
&self,
|
&self,
|
||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
@ -1004,6 +1014,8 @@ impl Engine {
|
|||||||
/// Evaluate a file, but throw away the result and only return error (if any).
|
/// Evaluate a file, but throw away the result and only return error (if any).
|
||||||
/// Useful for when you don't need the result, but still need to keep track of possible errors.
|
/// Useful for when you don't need the result, but still need to keep track of possible errors.
|
||||||
#[cfg(not(feature = "no_std"))]
|
#[cfg(not(feature = "no_std"))]
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
#[cfg(not(target_arch = "wasm64"))]
|
||||||
pub fn consume_file(&self, path: PathBuf) -> Result<(), Box<EvalAltResult>> {
|
pub fn consume_file(&self, path: PathBuf) -> Result<(), Box<EvalAltResult>> {
|
||||||
Self::read_file(path).and_then(|contents| self.consume(&contents))
|
Self::read_file(path).and_then(|contents| self.consume(&contents))
|
||||||
}
|
}
|
||||||
@ -1011,6 +1023,8 @@ impl Engine {
|
|||||||
/// Evaluate a file with own scope, but throw away the result and only return error (if any).
|
/// Evaluate a file with own scope, but throw away the result and only return error (if any).
|
||||||
/// Useful for when you don't need the result, but still need to keep track of possible errors.
|
/// Useful for when you don't need the result, but still need to keep track of possible errors.
|
||||||
#[cfg(not(feature = "no_std"))]
|
#[cfg(not(feature = "no_std"))]
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
#[cfg(not(target_arch = "wasm64"))]
|
||||||
pub fn consume_file_with_scope(
|
pub fn consume_file_with_scope(
|
||||||
&self,
|
&self,
|
||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
|
@ -292,8 +292,15 @@ impl Default for Engine {
|
|||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
#[cfg(not(feature = "no_std"))]
|
#[cfg(not(feature = "no_std"))]
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
#[cfg(not(target_arch = "wasm64"))]
|
||||||
module_resolver: Some(Box::new(resolvers::FileModuleResolver::new())),
|
module_resolver: Some(Box::new(resolvers::FileModuleResolver::new())),
|
||||||
#[cfg(any(feature = "no_module", feature = "no_std"))]
|
#[cfg(any(
|
||||||
|
feature = "no_module",
|
||||||
|
feature = "no_std",
|
||||||
|
target_arch = "wasm32",
|
||||||
|
target_arch = "wasm64"
|
||||||
|
))]
|
||||||
module_resolver: None,
|
module_resolver: None,
|
||||||
|
|
||||||
type_names: HashMap::new(),
|
type_names: HashMap::new(),
|
||||||
@ -373,6 +380,8 @@ fn extract_prop_from_setter(fn_name: &str) -> Option<&str> {
|
|||||||
/// Print/debug to stdout
|
/// Print/debug to stdout
|
||||||
fn default_print(s: &str) {
|
fn default_print(s: &str) {
|
||||||
#[cfg(not(feature = "no_std"))]
|
#[cfg(not(feature = "no_std"))]
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
#[cfg(not(target_arch = "wasm64"))]
|
||||||
println!("{}", s);
|
println!("{}", s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,6 +37,8 @@
|
|||||||
//! engine.register_fn("compute", compute_something);
|
//! engine.register_fn("compute", compute_something);
|
||||||
//!
|
//!
|
||||||
//! # #[cfg(not(feature = "no_std"))]
|
//! # #[cfg(not(feature = "no_std"))]
|
||||||
|
//! # #[cfg(not(target_arch = "wasm32"))]
|
||||||
|
//! # #[cfg(not(target_arch = "wasm64"))]
|
||||||
//! assert_eq!(
|
//! assert_eq!(
|
||||||
//! // Evaluate the script, expects a 'bool' return
|
//! // Evaluate the script, expects a 'bool' return
|
||||||
//! engine.eval_file::<bool>("my_script.rhai".into())?,
|
//! engine.eval_file::<bool>("my_script.rhai".into())?,
|
||||||
|
@ -1054,6 +1054,8 @@ pub trait ModuleResolver: SendSync {
|
|||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
pub mod resolvers {
|
pub mod resolvers {
|
||||||
#[cfg(not(feature = "no_std"))]
|
#[cfg(not(feature = "no_std"))]
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
#[cfg(not(target_arch = "wasm64"))]
|
||||||
pub use super::file::FileModuleResolver;
|
pub use super::file::FileModuleResolver;
|
||||||
pub use super::stat::StaticModuleResolver;
|
pub use super::stat::StaticModuleResolver;
|
||||||
}
|
}
|
||||||
@ -1063,6 +1065,8 @@ pub mod resolvers {}
|
|||||||
/// Script file-based module resolver.
|
/// Script file-based module resolver.
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
#[cfg(not(feature = "no_std"))]
|
#[cfg(not(feature = "no_std"))]
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
#[cfg(not(target_arch = "wasm64"))]
|
||||||
mod file {
|
mod file {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::stdlib::path::PathBuf;
|
use crate::stdlib::path::PathBuf;
|
||||||
|
@ -33,6 +33,8 @@ pub use pkg_std::StandardPackage;
|
|||||||
pub use string_basic::BasicStringPackage;
|
pub use string_basic::BasicStringPackage;
|
||||||
pub use string_more::MoreStringPackage;
|
pub use string_more::MoreStringPackage;
|
||||||
#[cfg(not(feature = "no_std"))]
|
#[cfg(not(feature = "no_std"))]
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
#[cfg(not(target_arch = "wasm64"))]
|
||||||
pub use time_basic::BasicTimePackage;
|
pub use time_basic::BasicTimePackage;
|
||||||
|
|
||||||
/// Trait that all packages must implement.
|
/// Trait that all packages must implement.
|
||||||
|
@ -13,6 +13,8 @@ use crate::stdlib::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(not(feature = "no_std"))]
|
#[cfg(not(feature = "no_std"))]
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
#[cfg(not(target_arch = "wasm64"))]
|
||||||
use crate::stdlib::path::PathBuf;
|
use crate::stdlib::path::PathBuf;
|
||||||
|
|
||||||
/// Evaluation result.
|
/// Evaluation result.
|
||||||
@ -29,6 +31,8 @@ pub enum EvalAltResult {
|
|||||||
///
|
///
|
||||||
/// Never appears under the `no_std` feature.
|
/// Never appears under the `no_std` feature.
|
||||||
#[cfg(not(feature = "no_std"))]
|
#[cfg(not(feature = "no_std"))]
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
#[cfg(not(target_arch = "wasm64"))]
|
||||||
ErrorReadingScriptFile(PathBuf, Position, std::io::Error),
|
ErrorReadingScriptFile(PathBuf, Position, std::io::Error),
|
||||||
|
|
||||||
/// Call to an unknown function. Wrapped value is the name of the function.
|
/// Call to an unknown function. Wrapped value is the name of the function.
|
||||||
@ -101,7 +105,9 @@ impl EvalAltResult {
|
|||||||
pub(crate) fn desc(&self) -> &str {
|
pub(crate) fn desc(&self) -> &str {
|
||||||
match self {
|
match self {
|
||||||
#[cfg(not(feature = "no_std"))]
|
#[cfg(not(feature = "no_std"))]
|
||||||
Self::ErrorReadingScriptFile(_, _, _) => "Cannot read from script file",
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
#[cfg(not(target_arch = "wasm64"))]
|
||||||
|
Self::ErrorReadingScriptFile(_, _, _) => "Cannot read from script file",
|
||||||
|
|
||||||
Self::ErrorParsing(p, _) => p.desc(),
|
Self::ErrorParsing(p, _) => p.desc(),
|
||||||
Self::ErrorInFunctionCall(_, _, _) => "Error in called function",
|
Self::ErrorInFunctionCall(_, _, _) => "Error in called function",
|
||||||
@ -160,6 +166,8 @@ impl fmt::Display for EvalAltResult {
|
|||||||
|
|
||||||
match self {
|
match self {
|
||||||
#[cfg(not(feature = "no_std"))]
|
#[cfg(not(feature = "no_std"))]
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
#[cfg(not(target_arch = "wasm64"))]
|
||||||
Self::ErrorReadingScriptFile(path, _, err) => {
|
Self::ErrorReadingScriptFile(path, _, err) => {
|
||||||
write!(f, "{} '{}': {}", desc, path.display(), err)?
|
write!(f, "{} '{}': {}", desc, path.display(), err)?
|
||||||
}
|
}
|
||||||
@ -259,6 +267,8 @@ impl EvalAltResult {
|
|||||||
pub fn position(&self) -> Position {
|
pub fn position(&self) -> Position {
|
||||||
match self {
|
match self {
|
||||||
#[cfg(not(feature = "no_std"))]
|
#[cfg(not(feature = "no_std"))]
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
#[cfg(not(target_arch = "wasm64"))]
|
||||||
Self::ErrorReadingScriptFile(_, pos, _) => *pos,
|
Self::ErrorReadingScriptFile(_, pos, _) => *pos,
|
||||||
|
|
||||||
Self::ErrorParsing(_, pos)
|
Self::ErrorParsing(_, pos)
|
||||||
@ -297,6 +307,8 @@ impl EvalAltResult {
|
|||||||
pub fn set_position(&mut self, new_position: Position) {
|
pub fn set_position(&mut self, new_position: Position) {
|
||||||
match self {
|
match self {
|
||||||
#[cfg(not(feature = "no_std"))]
|
#[cfg(not(feature = "no_std"))]
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
#[cfg(not(target_arch = "wasm64"))]
|
||||||
Self::ErrorReadingScriptFile(_, pos, _) => *pos = new_position,
|
Self::ErrorReadingScriptFile(_, pos, _) => *pos = new_position,
|
||||||
|
|
||||||
Self::ErrorParsing(_, pos)
|
Self::ErrorParsing(_, pos)
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
#![cfg(not(feature = "no_std"))]
|
#![cfg(not(feature = "no_std"))]
|
||||||
|
#![cfg(not(target_arch = "wasm32"))]
|
||||||
|
#![cfg(not(target_arch = "wasm64"))]
|
||||||
|
|
||||||
use rhai::{Engine, EvalAltResult, INT};
|
use rhai::{Engine, EvalAltResult, INT};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user