fov_entt_c
which contains some properties that users should configure. Based on these, the engine generates FOV requests that are delivered to and processed by cells responsible for hosting the region of interest. These target cells generate responses which are then coalesced and the final information is made available via one of the components.receive
which should be executed by the user at the start of the cell ticksend
which should be executed near the endaether/fov.hh
aether/entt.hh
).fov_entt_c<2>
or fov_entt_c<3>
.id
shape
predicate
refresh_updates
relevant
shape_t
:std::variant<geometry::circle, geometry::rectangle, geometry::trapezoid>
(2D)std::variant<geometry::sphere, geometry::cuboid, geometry::frustum>
(3D)origin
, radius
origin
, rotation
, width
, length
origin
, rotation
, view_angle
, near_distance
, far_distance
origin
, radius
origin
, rotation
, width
, length
, height
origin
, rotation
, view_angle
, near_distance
, far_distance
, aspect_ratio
predicate_t
found inside the FOV component. Predicates can be safely updated during runtime.fov_entt_c<2>
or fov_entt_c<3>
(i.e. no input components have been specified). Otherwise, specify at least one input component. Please see the section on input components for detail.color_c
. Our predicate examines this component in order to make a decision, and it would be undefined behaviour to process an entity without color_c
. Input components refers to the set of components that each entity must have in order to be considered.null_predicate_t
and users should not create instances of this type (set predicate
property to std::nullopt
). In the general case (non-empty set), the predicate type evaluates to predicate<PredicateComponents...>
.construct_predicate
utility function:comp_1
comp_2
comp_1
, comp_3
1
and 3
both have at least comp_1
and are thus included with the response, while entity 2
is discarded since it does not have comp_1
.select_components<everything>
. This tells the engine to include every component assigned to the entity. In this case, both comp_1
and comp_3
will be included with the response for entity 3
. The selection
type is quite powerful and allows us to return different combinations of component (as discussed in the next section).selection
. A single instance keeps track of the components selected for a single entity. You can return special objects:select_components<everything>{}
select_components<C1, C2, C3, ..., CN>{}
select_components<void>{}
<everything>
which selects each component assigned to the current component. We can also specify the exact components to return using <C1, C2, C3, ..., CN>{}
. The set of output components doesn't need to be a subset of components assigned the current entity (engine serializes as much as possible), but it cannot be empty. We can tell the engine to discard the current entity by returning <void>
.radar_c::strength
) must be explicitly copied to each target cell.size_t
as additional state which can be used inside the predicate to make decisions. When constructing the predicate, we provide a value for the state parameters. It is important that the type of the parameter matches the type of the value. In this case, the type of our literal is int
which needs to be converted to size_t
.std::placeholder
underneath and there is a limit to the maximum number of parameters. This is defined by the number of placeholders in your C++ implementation. But also note that predicates work more efficiently when used with few components (see section on optimisation). Later in this guide, we examine what's happening under the hood when constructing predicates.refresh_updates
property is used to control the frequency at which the relevant entity list is updated (more specifically, control the rate of request generation). Relevant entities are discussed in the next section. For now, let’s examine properties we can configure:frequency
controls how often FOV requests are generated, but the engine also checks start_tick
when making a decision. Setting the frequency to 0
effectively disables FOV for this particular entity. Setting it to 1
means the engine will update the relevant entity list each tick (default). Otherwise, a request is generated if current_tick - start_tick
is divisible by frequency
. If the frequency is greater than 1, the relevant entity list gets wiped until a response is ready.relevant_entities
property of the component is populated with EnTT IDs. Each ID represents a single relevant entity that can be found inside the registry of the current cell (hosting the entity using FOV). Components selected by the predicate are also available.relevant_entities
to avoid dangling pointers.refresh_updates
set to { frequency: 5, start_tick: 0 }
. This means an FOV request is genrated during ticks 0, 5, 10, and so on. Due to the FOV delay, we receive responses during ticks 2, 7, 12, and so on. This particular entity is a boss NPC tracking a very large group of players spread across cells. The predicate returns information such as player health and abilities. Once information has been coalesced, the boss entity decides whether or not it should initiate an attack. The decision itself is a single Boolean value which can be cached. An appropriate place would be a cache_c
component assigned to this entity.refresh_updates.frequency
and refresh_updates.start_tick
to remove any ambiguity.receive
processes FOV requests and responses and should be executed before cell logicsend
generates FOV requests and should be executed after cell logicsend
system requires a single template parameter:receive
system requires two additional template parameters:bool operator==(const self_t& other) const
std::tuple
) used in the simulation, only these components are copied across cells, this set must be a superset of the predicate output componentsreceive
and send
systems per FOV type. If our FOV type is fov_entt_c<2, player_c>
, potential system types are:receive<fov_entt_c<2, player_c>, unique_c, std::tuple<player_c, position_c>>
send<fov_entt_c<2, player_c>>
player_c
. We cannot determine the exact set of output components based on the type alone. However, it must be a subset of { player_c, position_c }
. This means our predicate must return one of:select_components<everything>
select_components<player_c>
select_components<position_c>
select_components<player_c, position_c>
(order of components does not matter)select_components<void>
select_components<everything>
, this means serialize each interesting component that is also attached to the current entity. So in this particular case, returning everything is equivalent to returning select_components<player_c, position_c>
.cell_tick
member function:get_agent_position
argument. This is a closure which accepts aether_state_type
and agent_reference
(these types are automatically available inside the user cell state implementation), and returns the position of the specified agent. Here is a possible implementation:select_components<everything>
and only return what's necessaryconstruct_predicate
function is provided as a convenience:construct_predicate
, the user state parameters can be of type T
or const T
. It doesn't make sense to use T&
because individual invocations of the predicate should not interact with each other. Even if relying on this behaviour provided some sort of interesting functionality, it would be difficult to reason about it since predicates may be executing on different cells. But of course, it does makes sense to use const T&
. Unfortunately, this not possible not possible due to the current implementation of construct_predicate
(there is a ticket to fix this).const T&
if you don't mind directly interacting with aether::fov::predicate
. This type is defined as:aether::closure
. Explaining the usage of this type is outside of the scope of this guide. Instead, here is a working example:T&
but this results in undefined behaviour in the context of FOV predicates.