Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
ea39930
21800: Begins implementation of immediate return request types
howsohazard Dec 9, 2025
c1a43b4
21800: Minor progress
howsohazard Dec 10, 2025
9315765
21800: Minor improvement
howsohazard Dec 11, 2025
7c5bcde
Merge branch 'main' into 21800-immediate-return-types
howsohazard Dec 11, 2025
69186b7
21800: Refactoring
howsohazard Dec 11, 2025
86c893e
21800: More progress
howsohazard Dec 12, 2025
58c89a8
21800: Improves consistency of immediate value handling
howsohazard Dec 13, 2025
1aab1f5
21800: Fixes and debugability
howsohazard Dec 15, 2025
1ef48cb
21800: Adds specialization
howsohazard Dec 15, 2025
0806a1b
21800: More todos
howsohazard Dec 15, 2025
071355a
21800: More progress
howsohazard Dec 16, 2025
17f5ce2
21800: More implementation
howsohazard Dec 17, 2025
9c1c79a
Merge branch 'main' into 21800-immediate-return-types
howsohazard Dec 17, 2025
7d0e203
21800: More implementation
howsohazard Dec 17, 2025
8e17c8d
21800: Finishes implementation, at least one bug remains
howsohazard Dec 17, 2025
404ac76
21800: Logic fixes
howsohazard Dec 18, 2025
18c7afa
21800: More logic fixes
howsohazard Dec 18, 2025
ee7d3d4
21800: Logic fix
howsohazard Dec 18, 2025
f2d1a72
21800: More fixes, but at least one more bug remains
howsohazard Dec 18, 2025
ad283f6
21800: Fixes remaining known bug
howsohazard Dec 18, 2025
2a8d21d
Merge branch 'main' into 21800-immediate-return-types
howsohazard Dec 19, 2025
387e51a
Merge branch 'main' into 21800-immediate-return-types
howsohazard Dec 23, 2025
5b853d8
Merge branch 'main' into 21800-immediate-return-types
howsohazard Dec 28, 2025
ef0e298
Merge branch '21800-immediate-return-types' of https://github.com/how…
howsohazard Dec 30, 2025
81f53ca
Merge branch 'main' into 21800-immediate-return-types
howsohazard Dec 30, 2025
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
29 changes: 28 additions & 1 deletion debug-visualizers/AmalgamInternalTypes.natvis
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,35 @@
</Expand>
</Type>

<Type Name="EvaluableNodeRequestedValueTypes">
<DisplayString Condition="requestedValueTypes == 0">NONE</DisplayString>
<DisplayString Condition="requestedValueTypes == 1">NULL_VALUE</DisplayString>
<DisplayString Condition="requestedValueTypes == 2">BOOL</DisplayString>
<DisplayString Condition="requestedValueTypes == 4">NUMBER</DisplayString>
<DisplayString Condition="requestedValueTypes == 8">EXISTING_STRING_ID</DisplayString>
<DisplayString Condition="requestedValueTypes == 16">STRING_ID</DisplayString>
<DisplayString Condition="requestedValueTypes == 64">CODE</DisplayString>
<DisplayString Condition="requestedValueTypes == 3">REQUEST_BOOL</DisplayString>
<DisplayString Condition="requestedValueTypes == 5">REQUEST_NUMBER</DisplayString>
<DisplayString Condition="requestedValueTypes == 9">REQUEST_EXISTING_STRING_ID</DisplayString>
<DisplayString Condition="requestedValueTypes == 17">REQUEST_STRING_ID</DisplayString>
<DisplayString Condition="requestedValueTypes == 63">ALL</DisplayString>
<DisplayString>{requestedValueTypes}</DisplayString>

<Expand>
<Item Name="Raw Value">requestedValueTypes</Item>
<Item Name="NULL_VALUE">(requestedValueTypes &amp; 1) != 0</Item>
<Item Name="BOOL">(requestedValueTypes &amp; 2) != 0</Item>
<Item Name="NUMBER">(requestedValueTypes &amp; 4) != 0</Item>
<Item Name="EXISTING_STRING_ID">(requestedValueTypes &amp; 8) != 0</Item>
<Item Name="STRING_ID">(requestedValueTypes &amp; 16) != 0</Item>
<Item Name="STRING">(requestedValueTypes &amp; 32) != 0</Item>
<Item Name="CODE">(requestedValueTypes &amp; 64) != 0</Item>
</Expand>
</Type>

<Type Name="EntityPermissions">
<DisplayString Condition="allPermissions == 0">None</DisplayString>
<DisplayString Condition="allPermissions == 0">NONE</DisplayString>
<DisplayString Condition="allPermissions == 1">STD_OUT_AND_STD_ERR</DisplayString>
<DisplayString Condition="allPermissions == 2">STD_IN</DisplayString>
<DisplayString Condition="allPermissions == 4">LOAD</DisplayString>
Expand Down
18 changes: 10 additions & 8 deletions src/Amalgam/entity/Entity.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,9 @@ Entity::~Entity()
string_intern_pool.DestroyStringReferences(labelIndex, [](auto l) { return l.first; });
}

std::pair<EvaluableNodeReference, bool> Entity::GetValueAtLabel(StringInternPool::StringID label_sid, EvaluableNodeManager *destination_temp_enm, bool direct_get, bool on_self, bool batch_call)
std::pair<EvaluableNodeReference, bool> Entity::GetValueAtLabel(
StringInternPool::StringID label_sid, EvaluableNodeManager *destination_temp_enm,
bool direct_get, EvaluableNodeRequestedValueTypes immediate_result, bool on_self, bool batch_call)
{
if(label_sid == string_intern_pool.NOT_A_STRING_ID)
return std::pair(EvaluableNodeReference::Null(), false);
Expand All @@ -137,16 +139,16 @@ std::pair<EvaluableNodeReference, bool> Entity::GetValueAtLabel(StringInternPool
if(label == end(labelIndex))
return std::pair(EvaluableNodeReference::Null(), false);

if(label->second == nullptr)
return std::pair(EvaluableNodeReference::Null(), true);

EvaluableNodeReference retval(label->second, false);
auto retval = EvaluableNodeReference::CoerceNonUniqueEvaluableNodeToImmediateIfPossible(label->second, immediate_result);
if(retval.IsImmediateValue())
return std::pair(retval, true);

//if didn't give a valid destination, just return what we have
if(destination_temp_enm == nullptr)
return std::pair(retval, true);

return std::pair(destination_temp_enm->DeepAllocCopy(retval, direct_get ? EvaluableNodeManager::ENMM_NO_CHANGE : EvaluableNodeManager::ENMM_REMOVE_ALL), true);
return std::pair(destination_temp_enm->DeepAllocCopy(retval,
direct_get ? EvaluableNodeManager::ENMM_NO_CHANGE : EvaluableNodeManager::ENMM_REMOVE_ALL), true);
}

std::pair<bool, bool> Entity::GetValueAtLabelAsBool(StringInternPool::StringID label_sid, bool on_self)
Expand Down Expand Up @@ -387,8 +389,8 @@ std::pair<bool, bool> Entity::SetValuesAtLabels(EvaluableNodeReference new_label
{
//need to make a copy in case it is modified, so pass in evaluableNodeManager
EvaluableNodeReference value_destination_node(
GetValueAtLabel(variable_sid, &evaluableNodeManager, true, true, true).first,
true);
GetValueAtLabel(variable_sid, &evaluableNodeManager, true,
EvaluableNodeRequestedValueTypes::Type::NONE, true, true).first, true);
//can't assign to a label if it doesn't exist
if(value_destination_node == nullptr)
continue;
Expand Down
16 changes: 11 additions & 5 deletions src/Amalgam/entity/Entity.h
Original file line number Diff line number Diff line change
Expand Up @@ -390,15 +390,20 @@ class Entity
// If destination_temp_enm is nullptr, it will return the node reference directly.
// If direct_get is true, then it will return values with all labels
// If on_self is true, then it will be allowed to access private variables
// If batch_call is true, then it assumes it will be called in a batch of updates and will not perform any cleanup or synchronization
std::pair<EvaluableNodeReference, bool> GetValueAtLabel(StringInternPool::StringID label_sid, EvaluableNodeManager *destination_temp_enm, bool direct_get,
// If batch_call is true, then it assumes it will be called in a batch of updates and will
// not perform any cleanup or synchronization
std::pair<EvaluableNodeReference, bool> GetValueAtLabel(StringInternPool::StringID label_sid,
EvaluableNodeManager *destination_temp_enm, bool direct_get,
EvaluableNodeRequestedValueTypes immediate_result = EvaluableNodeRequestedValueTypes(),
bool on_self = false, bool batch_call = false);

//same as GetValueAtLabel but accepts a string for label_name
inline std::pair<EvaluableNodeReference, bool> GetValueAtLabel(const std::string &label_name, EvaluableNodeManager *destination_temp_enm, bool direct_get, bool on_self = false)
inline std::pair<EvaluableNodeReference, bool> GetValueAtLabel(const std::string &label_name,
EvaluableNodeManager *destination_temp_enm, bool direct_get,
EvaluableNodeRequestedValueTypes immediate_result = EvaluableNodeRequestedValueTypes(), bool on_self = false)
{
StringInternPool::StringID label_sid = string_intern_pool.GetIDFromString(label_name);
return GetValueAtLabel(label_sid, destination_temp_enm, direct_get, on_self);
return GetValueAtLabel(label_sid, destination_temp_enm, direct_get, immediate_result, on_self);
}

//Returns true if the label specified by label_sid exists
Expand Down Expand Up @@ -446,7 +451,8 @@ class Entity
{
for(auto &[label_id, _] : labelIndex)
{
EvaluableNode *node = GetValueAtLabel(label_id, destination_temp_enm, direct_get, on_self, true).first;
EvaluableNode *node = GetValueAtLabel(label_id, destination_temp_enm, direct_get,
EvaluableNodeRequestedValueTypes::Type::NONE, on_self, true).first;
if(node != nullptr)
func(label_id, node);
}
Expand Down
18 changes: 9 additions & 9 deletions src/Amalgam/entity/EntityQueryCaches.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -924,7 +924,7 @@ void EntityQueryCaches::GetMatchingEntitiesViaSamplingWithReplacement(EntityQuer

EvaluableNodeReference EntityQueryCaches::GetMatchingEntitiesFromQueryCaches(Entity *container,
std::vector<EntityQueryCondition> &conditions, EvaluableNodeManager *enm,
bool return_query_value, bool immediate_result)
bool return_query_value, EvaluableNodeRequestedValueTypes immediate_result)
{
//get the cache associated with this container
// use the first condition as an heuristic for building it if it doesn't exist
Expand Down Expand Up @@ -956,7 +956,7 @@ EvaluableNodeReference EntityQueryCaches::GetMatchingEntitiesFromQueryCaches(Ent
//if query_none, return results as empty list
if(cond.queryType == ENT_NULL)
{
if(immediate_result)
if(immediate_result.Allows(EvaluableNodeRequestedValueTypes::Type::NUMBER))
return EvaluableNodeReference(0.0);
return EvaluableNodeReference(enm->AllocNode(ENT_LIST), true);
}
Expand Down Expand Up @@ -1061,7 +1061,7 @@ EvaluableNodeReference EntityQueryCaches::GetMatchingEntitiesFromQueryCaches(Ent
FastHashMap<StringInternPool::StringID, double> value_weights;
entity_caches->ComputeValuesFromMatchingEntities(&cond, matching_ents, value_weights, is_first);

if(immediate_result)
if(immediate_result.AnyImmediateType())
{
double num_results = static_cast<double>(value_weights.size());
for(auto &[value, weight] : value_weights)
Expand Down Expand Up @@ -1214,7 +1214,7 @@ EvaluableNodeReference EntityQueryCaches::GetMatchingEntitiesFromQueryCaches(Ent
//if last query condition is query sample, return each sampled entity id which may include duplicates
if(last_query_type == ENT_QUERY_SAMPLE)
{
if(immediate_result)
if(immediate_result.AnyImmediateType())
return EvaluableNodeReference(static_cast<double>(indices_with_duplicates.size()));

return CreateListOfStringsIdsFromIteratorAndFunction(indices_with_duplicates, enm, entity_index_to_id);
Expand All @@ -1227,7 +1227,7 @@ EvaluableNodeReference EntityQueryCaches::GetMatchingEntitiesFromQueryCaches(Ent

if(last_query_type == ENT_QUERY_DISTANCE_CONTRIBUTIONS)
{
if(immediate_result)
if(immediate_result.AnyImmediateType())
return EvaluableNodeReference(static_cast<double>(compute_results.size()));

return CreateListOfNumbersFromIteratorAndFunction(compute_results, enm,
Expand All @@ -1240,7 +1240,7 @@ EvaluableNodeReference EntityQueryCaches::GetMatchingEntitiesFromQueryCaches(Ent
|| last_query_type == ENT_QUERY_ENTITY_KL_DIVERGENCES
|| last_query_type == ENT_QUERY_ENTITY_CUMULATIVE_NEAREST_ENTITY_WEIGHTS)
{
if(immediate_result)
if(immediate_result.AnyImmediateType())
return EvaluableNodeReference(static_cast<double>(compute_results.size()));

return EntityManipulation::ConvertResultsToEvaluableNodes<size_t>(compute_results,
Expand All @@ -1249,7 +1249,7 @@ EvaluableNodeReference EntityQueryCaches::GetMatchingEntitiesFromQueryCaches(Ent
}
else //if there are no compute results, return an assoc of the requested labels for each entity
{
if(immediate_result)
if(immediate_result.AnyImmediateType())
return EvaluableNodeReference(static_cast<double>(matching_ents.size()));

//return assoc of distances if requested
Expand Down Expand Up @@ -1286,14 +1286,14 @@ EvaluableNodeReference EntityQueryCaches::GetMatchingEntitiesFromQueryCaches(Ent
}
}

if(immediate_result)
if(immediate_result.AnyImmediateType())
return EvaluableNodeReference(static_cast<double>(matching_ents.size()));
return CreateListOfStringsIdsFromIteratorAndFunction(matching_ents, enm, entity_index_to_id);
}


EvaluableNodeReference EntityQueryCaches::GetEntitiesMatchingQuery(EntityReadReference &container,
std::vector<EntityQueryCondition> &conditions, EvaluableNodeManager *enm, bool return_query_value, bool immediate_result)
std::vector<EntityQueryCondition> &conditions, EvaluableNodeManager *enm, bool return_query_value, EvaluableNodeRequestedValueTypes immediate_result)
{
if(_enable_SBF_datastore && CanUseQueryCaches(conditions))
{
Expand Down
4 changes: 2 additions & 2 deletions src/Amalgam/entity/EntityQueryCaches.h
Original file line number Diff line number Diff line change
Expand Up @@ -121,13 +121,13 @@ class EntityQueryCaches
// if immediate_result is true, will return an immediate value as the result
static EvaluableNodeReference GetEntitiesMatchingQuery(EntityReadReference &container,
std::vector<EntityQueryCondition> &conditions, EvaluableNodeManager *enm,
bool return_query_value, bool immediate_result);
bool return_query_value, EvaluableNodeRequestedValueTypes immediate_result);

//returns the collection of entities (and optionally associated compute values) that satisfy the specified chain of query conditions
// uses efficient querying methods with a query database, one database per container
static EvaluableNodeReference GetMatchingEntitiesFromQueryCaches(Entity *container,
std::vector<EntityQueryCondition> &conditions, EvaluableNodeManager *enm,
bool return_query_value, bool immediate_result);
bool return_query_value, EvaluableNodeRequestedValueTypes immediate_result);

//the container this is a cache for
Entity *container;
Expand Down
116 changes: 116 additions & 0 deletions src/Amalgam/evaluablenode/EvaluableNode.h
Original file line number Diff line number Diff line change
Expand Up @@ -1190,6 +1190,122 @@ enum EvaluableNodeImmediateValueType : uint8_t
ENIVT_STRING_ID_INDIRECTION_INDEX //not a real EvaluableNode type, but an index to some data structure that has a stringID
};

//when an EvaluableNodeImmediateValue is requested, this class can indicate which types of values are allowed
// EvaluableNodeRequestedValueTypes.h
class EvaluableNodeRequestedValueTypes
{
public:
using StorageType = uint8_t;

enum class Type : StorageType
{
NONE = 0,
NULL_VALUE = 1 << 0,
BOOL = 1 << 1,
NUMBER = 1 << 2,
EXISTING_STRING_ID = 1 << 3,
STRING_ID = 1 << 4,
EXISTING_KEY_STRING_ID = 1 << 5,
KEY_STRING_ID = 1 << 6,
CODE = 1 << 7,

//composite types which can include NULL_VALUE
REQUEST_BOOL = BOOL | NULL_VALUE,
REQUEST_NUMBER = NUMBER | NULL_VALUE,
REQUEST_EXISTING_STRING_ID = EXISTING_STRING_ID | NULL_VALUE,
REQUEST_STRING_ID = STRING_ID | NULL_VALUE,
REQUEST_EXISTING_KEY_STRING_ID = EXISTING_KEY_STRING_ID | NULL_VALUE,
REQUEST_KEY_STRING_ID = KEY_STRING_ID | NULL_VALUE,

ALL = NULL_VALUE | BOOL | NUMBER | EXISTING_STRING_ID | STRING_ID | CODE
};

constexpr EvaluableNodeRequestedValueTypes() noexcept
: requestedValueTypes(Type::NONE)
{}

constexpr EvaluableNodeRequestedValueTypes(Type t) noexcept
: requestedValueTypes(t)
{}

constexpr EvaluableNodeRequestedValueTypes(StorageType raw) noexcept
: requestedValueTypes(static_cast<Type>(raw))
{}

//boolean implies all or none
constexpr EvaluableNodeRequestedValueTypes(bool all_or_none) noexcept
: requestedValueTypes(all_or_none ? Type::ALL : Type::NONE)
{}

//bit‑wise operators
constexpr EvaluableNodeRequestedValueTypes &operator|=(EvaluableNodeRequestedValueTypes rhs) noexcept
{
requestedValueTypes = static_cast<Type>(static_cast<StorageType>(requestedValueTypes) |
static_cast<StorageType>(rhs.requestedValueTypes));
return *this;
}

constexpr EvaluableNodeRequestedValueTypes &operator&=(EvaluableNodeRequestedValueTypes rhs) noexcept
{
requestedValueTypes = static_cast<Type>(static_cast<StorageType>(requestedValueTypes) &
static_cast<StorageType>(rhs.requestedValueTypes));
return *this;
}

constexpr friend EvaluableNodeRequestedValueTypes operator|(
EvaluableNodeRequestedValueTypes lhs,
EvaluableNodeRequestedValueTypes rhs) noexcept
{
return EvaluableNodeRequestedValueTypes(
static_cast<Type>(static_cast<StorageType>(lhs.requestedValueTypes) |
static_cast<StorageType>(rhs.requestedValueTypes)));
}

constexpr friend EvaluableNodeRequestedValueTypes operator&(
EvaluableNodeRequestedValueTypes lhs,
EvaluableNodeRequestedValueTypes rhs) noexcept
{
return EvaluableNodeRequestedValueTypes(
static_cast<Type>(static_cast<StorageType>(lhs.requestedValueTypes) &
static_cast<StorageType>(rhs.requestedValueTypes)));
}

constexpr friend EvaluableNodeRequestedValueTypes operator~(
EvaluableNodeRequestedValueTypes v) noexcept
{
return EvaluableNodeRequestedValueTypes(
static_cast<Type>(~static_cast<StorageType>(v.requestedValueTypes)));
}

constexpr bool Allows(Type flag) const noexcept
{
return (static_cast<StorageType>(requestedValueTypes) &
static_cast<StorageType>(flag)) != 0;
}

//returns true if any immediate is allowed
constexpr bool AnyImmediateType() const noexcept
{
return (static_cast<StorageType>(requestedValueTypes) & ~static_cast<StorageType>(Type::CODE)) != 0;
}

//returns true if an immediate is allowed
constexpr bool ImmediateValue() const noexcept
{
return requestedValueTypes != Type::NONE;
}

//returns true if an immediate value is allowed but immediate types are not allowed
constexpr bool ImmediateValueButNotImmediateType() const noexcept
{
return (static_cast<StorageType>(requestedValueTypes) & ~static_cast<StorageType>(Type::CODE)) == 0;
}

private:
Type requestedValueTypes;
};


//structure that can hold the most immediate value type of an EvaluableNode
// EvaluableNodeImmediateValueType can be used to communicate which type of data is being held
union EvaluableNodeImmediateValue
Expand Down
Loading