Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61,677 changes: 33,489 additions & 28,188 deletions distr/flecs.c

Large diffs are not rendered by default.

620 changes: 592 additions & 28 deletions distr/flecs.h

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion distr/flecs_no_addons.h
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,9 @@
#ifdef FLECS_NO_SCRIPT_MATH
#undef FLECS_SCRIPT_MATH
#endif
#ifdef FLECS_NO_SCRIPT_ASYNC
#undef FLECS_SCRIPT_ASYNC
#endif
#ifdef FLECS_NO_SCRIPT_PLATFORM
#undef FLECS_SCRIPT_PLATFORM
#endif
Expand Down Expand Up @@ -314,7 +317,8 @@
/* Resolve addon dependencies before addon-dependent API declarations. The
* order of these blocks follows the addon dependency graph, from addons with
* the most dependencies to addons with the least dependencies. */
#if defined(FLECS_SCRIPT_MATH) || defined(FLECS_SCRIPT_PLATFORM)
#if defined(FLECS_SCRIPT_ASYNC) || defined(FLECS_SCRIPT_MATH) || \
defined(FLECS_SCRIPT_PLATFORM)
#endif

#if defined(FLECS_ALERTS) || defined(FLECS_APP) || defined(FLECS_TIMER)
Expand Down
173 changes: 146 additions & 27 deletions docs/FlecsScript.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,17 @@ my_entity {
When referring to child entities or components, identifiers need to include the parent path as well as the entity name. Paths are provided as lists of identifiers separated by a dot (`.`):

```cpp
Sun {
Earth {
solarsystem.Planet
}
}
```

Paths can only be used to refer to existing entities. The name of an entity that's created by a script cannot be a path:

```cpp
// Invalid, entity names cannot be paths
Sun.Earth {
solarsystem.Planet
}
Expand Down Expand Up @@ -360,7 +371,7 @@ Initializers are values that are used to initialize composite and collection mem
{x += 10, y *= 2}
```

Initializers must always be assigned to an lvalue of a well defined type. This can either be a typed variable, component assignment, function parameter or in the case of nested initializers, an element of another initializer. For example, this is a valid usage of an initializer:
Composite initializers must always be assigned to an lvalue of a well defined type. This can either be a typed variable, component assignment, function parameter or in the case of nested initializers, an element of another initializer. For example, this is a valid usage of an initializer:

```cpp
const x: Position = {10, 20}
Expand All @@ -373,6 +384,25 @@ while this is an invalid usage of an initializer:
const x = {10, 20}
```

Collection initializers do not require a well defined type (see vector literals).

### Vector literals
When a collection initializer is not assigned to an lvalue of a well defined type, it evaluates to a vector. The element type of the vector is derived from the initializer elements, where the most expressive element type determines the vector type:

```cpp
const a = [10, 20, 30] // vector<i64>
const b = [10, 10.5, 20] // vector<f64>
const c = ["foo", "bar"] // vector<string>
```

Element types that cannot be implicitly converted to each other, such as numbers and strings, cannot be mixed in the same vector literal.

Ranges can also be assigned, in which case they materialize into a vector with the values in the range (the end of the range is exclusive):

```cpp
const v = [1 .. 5] // vector<i32> [1, 2, 3, 4]
```

When assigning variables to elements in a composite initializer, applications can use the following shorthand notation if the variable names are the same as the member name of the element:

```cpp
Expand Down Expand Up @@ -565,7 +595,7 @@ e {
}
```

A new expression may only create a single entity, so comma operators are not supported.
A new expression may only create a single entity.

### String interpolation
Flecs script supports interpolated strings, which are strings that can contain expressions. String interpolation supports two forms, where one allows for easy embedding of variables, whereas the other allows for embedding any kind of expression. The following example shows an embedded variable:
Expand Down Expand Up @@ -1240,6 +1270,8 @@ struct Position(x: f32, y: f32)

The `components.transform` entity will be created with the `Module` tag.

The `module` statement must be the first statement of a script.

## Include statement
The `include` statement loads another script file. Example:

Expand All @@ -1254,7 +1286,7 @@ If the included path does not end in `.flecs`, the extension is appended automat

When `include` is used from a managed script (see [Managed script](#managed-script)), the included script is also loaded as a managed script. If a managed script at that path already exists, it is not loaded again. When used from a non-managed script, the included script is executed in place and no script entity is created.

The `include` statement is only allowed at the root scope of a script, and cannot appear inside a template.
The `include` statement is only allowed at the root scope of a script, and cannot appear inside a template. It must appear before any statement other than `module` and other `include` statements.

## Using statement
The `using` keyword imports a namespace into the current namespace. Example:
Expand All @@ -1274,16 +1306,18 @@ my_engine {
}
```

The `using` keyword only applies to the scope in which it is specified. Example:
A `using` statement must appear at the top of a script, after any `module` and `include` statements, and before any other statement. It is not allowed inside scopes or templates. Example:

```cpp
// Scope without using
my_engine {
game.engines.FtlEngine: {active: true}
// OK
using game.engines

my_spaceship {
FtlEngine: {active: true}
}
```
```cpp
// Scope with using
// Not OK: using may not appear inside a scope
my_spaceship {
using game.engines

Expand Down Expand Up @@ -1493,6 +1527,53 @@ world.import<math>();
double pi_2 = math::pi * 2;
```

#### Mutable exported variables
An exported variable declared with `const` is a compile time constant. Its value
is folded into every expression that uses it, which means that changing the value
afterwards does not affect scripts that already ran.

When a value has to change after a script ran, declare it with `mut` instead of
`const`. The value of a `mut` variable is never folded, and scripts that use it
are reevaluated when it changes:

```cpp
// Script 1
export mut difficulty: f32 = 1.0
```

```cpp
// Script 2
enemy {
Health: {100 * difficulty}
}
```

The `ecs_mut_var_init` function is used to create mutable exported variables from
C code:

```cpp
float difficulty_value = 1.0;

ecs_entity_t difficulty = ecs_mut_var(world, {
.name = "difficulty",
.type = ecs_id(ecs_f32_t),
.value = &difficulty_value
});
```

To change the value of a mut variable, obtain a pointer to it with
`ecs_mut_var_get`, and signal the change with `ecs_mut_var_modified`. Scripts
that use the variable are reevaluated:

```cpp
ecs_value_t v = ecs_mut_var_get(world, difficulty);
*(float*)v.ptr = 2.0;
ecs_mut_var_modified(world, difficulty);
```

Note that a script is not reevaluated by a change to a mut variable that the
script declares itself.

## Component values
A script can use the value of a component that is looked up on a specific entity. The following example fetches the `width` and `depth` members from the `Level` component, that is fetched from the `Game` entity:

Expand Down Expand Up @@ -1593,6 +1674,63 @@ for i in 0..10 {
}
```

Ranges can also be enclosed in brackets:

```cpp
for i in [0..10] {
// ...
}
```

A range loop can be given a second loop variable, in which case the first
variable is the zero-based iteration index and the second variable is the range
value:

```cpp
for (index, value) in [5..10] {
// (0, 5), (1, 6), ... (4, 9)
}
```

### Collection iteration
For loops can also iterate the elements of arrays, vectors and maps:

```cpp
for elem in arrayExpr {
_ { Position: {elem, elem * 2} }
}
```

Arrays and vectors can be iterated with an additional index variable, which
contains the zero-based index of the current element:

```cpp
for (index, elem) in arrayExpr {
"e_{index}" { Position: {elem, elem * 2} }
}
```

Maps can be iterated with up to three loop variables. With a single variable
the loop iterates the map values. When two variables are specified, the first
variable contains the key of the current element. A third variable can be added
in the middle, which contains the zero-based iteration index:

```cpp
for elem in mapExpr {
_ { Position: {elem, elem * 2} }
}

for (key, elem) in mapExpr {
"e_{key}" { Position: {elem, elem * 2} }
}

for (key, index, elem) in mapExpr {
"e_{key}" { Position: {index, elem * 2} }
}
```

Note that the iteration order of maps is undefined.

## Type definitions
Scripts can define component types by using the type entities from the `flecs.meta` module (`struct`, `enum`, `bitmask`) as entity kind, followed by an initializer list that describes the type.

Expand Down Expand Up @@ -1691,25 +1829,6 @@ my_spaceship {
}
```

## Comma operator
The comma operator can be used as a shortcut to create multiple entities in a scope. Example:

```cpp
my_spaceship {
pilot_a,
pilot_b,
pilot_c
}

// is equivalent to

my_spaceship {
pilot_a {}
pilot_b {}
pilot_c {}
}
```

## API
This section goes over how to run scripts in an application.

Expand Down
9 changes: 6 additions & 3 deletions examples/script/hello_world.flecs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,10 @@ my_spaceship {
}
}

// The dot notation can be used to refer to nested entities
my_spaceship.engine {
FasterThanLight
// A scope can be reopened to add more to an existing entity. Note that scopes
// can only be opened for a single entity name, not for a path (a.b).
my_spaceship {
engine {
FasterThanLight
}
}
1 change: 1 addition & 0 deletions include/flecs.h
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@
#define FLECS_PARSER /**< Utilities for script and query DSL parsers. */
#define FLECS_QUERY_DSL /**< Flecs query DSL parser. */
#define FLECS_SCRIPT /**< Flecs entity notation language. */
#define FLECS_SCRIPT_ASYNC /**< Async/await support for Flecs script. */
// #define FLECS_SCRIPT_MATH /**< Math functions for Flecs script (may require linking with libm). */
// #define FLECS_SCRIPT_PLATFORM /**< Platform constants for Flecs script. */
#define FLECS_SYSTEM /**< System support. */
Expand Down
59 changes: 59 additions & 0 deletions include/flecs/addons/cpp/mixins/script/impl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,24 @@ namespace _ {
return value;
}

inline ecs_value_t get_mut_var(const flecs::world_t *world, const char *name) {
flecs::entity_t v = ecs_lookup_path_w_sep(
world, 0, name, "::", "::", false);
if (!v) {
ecs_warn("unresolved mut variable '%s', returning default", name);
return {};
}

ecs_value_t value = ecs_mut_var_get(world, v);
if (value.ptr == nullptr) {
ecs_warn("entity '%s' is not a mut variable, returning default",
name);
return {};
}

return value;
}

template <typename T>
inline T get_const_value(
flecs::world_t *world, const char *name, ecs_value_t value, ecs_entity_t type, const T& default_value)
Expand Down Expand Up @@ -189,6 +207,47 @@ void world::get_const_var(
world_, name, value, type, default_value);
}

template <typename T>
inline T world::get_mut_var(
const char *name,
const T& default_value) const
{
ecs_value_t value = flecs::_::get_mut_var(world_, name);
if (!value.ptr) {
return default_value;
}

flecs::id_t type = flecs::_::type<T>::id(world_);
if (type == value.type) {
return *(static_cast<T*>(value.ptr));
}

return flecs::_::get_const_value<T>(
world_, name, value, type, default_value);
}

template <typename T>
void world::get_mut_var(
const char *name,
T& out,
const T& default_value) const
{
ecs_value_t value = flecs::_::get_mut_var(world_, name);
if (!value.ptr) {
out = default_value;
return;
}

flecs::id_t type = flecs::_::type<T>::id(world_);
if (type == value.type) {
out = *(static_cast<T*>(value.ptr));
return;
}

out = flecs::_::get_const_value<T>(
world_, name, value, type, default_value);
}


namespace script {
namespace _ {
Expand Down
Loading