Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
17 changes: 15 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ if (CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
"${CMAKE_CURRENT_SOURCE_DIR}/src/circuit/grpc/CommandQueue.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/circuit/grpc/LiveControlState.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/circuit/grpc/Counters.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/circuit/grpc/StockQueueReader.cpp")
"${CMAKE_CURRENT_SOURCE_DIR}/src/circuit/grpc/StockQueueReader.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/circuit/grpc/StockQueueTrace.cpp")
# The tactical helper lands in the native implementation lane. Keep this
# contract checkpoint independently buildable while registering the helper
# as soon as the coherent join supplies it.
Expand Down Expand Up @@ -119,6 +120,16 @@ if (CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
add_test(NAME stock_queue_reader_test COMMAND stock_queue_reader_test)
endif ()

if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/tests/unit/stock_queue_trace_test.cc")
add_executable(stock_queue_trace_test
"${CMAKE_CURRENT_SOURCE_DIR}/tests/unit/stock_queue_trace_test.cc")
target_include_directories(stock_queue_trace_test PRIVATE
"${CMAKE_CURRENT_SOURCE_DIR}/src/circuit")
target_link_libraries(stock_queue_trace_test PRIVATE
highbar_command_queue_test_support GTest::gtest GTest::gtest_main)
add_test(NAME stock_queue_trace_test COMMAND stock_queue_trace_test)
endif ()

if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/tests/unit/tactical_feature_capacity_test.cc")
add_executable(tactical_feature_capacity_test
"${CMAKE_CURRENT_SOURCE_DIR}/tests/unit/tactical_feature_capacity_test.cc")
Expand Down Expand Up @@ -391,7 +402,8 @@ constexpr const char* kEngineSha256 = "@HIGHBAR_ENGINE_SHA256@";
"${CMAKE_CURRENT_SOURCE_DIR}/src/circuit/grpc/CommandQueue.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/circuit/grpc/LiveControlState.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/circuit/grpc/Counters.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/circuit/grpc/StockQueueReader.cpp")
"${CMAKE_CURRENT_SOURCE_DIR}/src/circuit/grpc/StockQueueReader.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/circuit/grpc/StockQueueTrace.cpp")
if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/circuit/grpc/TacticalNativeState.cpp")
target_sources(highbar_command_queue_test_support PRIVATE
"${CMAKE_CURRENT_SOURCE_DIR}/src/circuit/grpc/TacticalNativeState.cpp")
Expand Down Expand Up @@ -484,6 +496,7 @@ constexpr const char* kEngineSha256 = "@HIGHBAR_ENGINE_SHA256@";
OR _test_name STREQUAL "live_control_state_test"
OR _test_name STREQUAL "tactical_native_state_test"
OR _test_name STREQUAL "stock_queue_reader_test"
OR _test_name STREQUAL "stock_queue_trace_test"
OR _test_name STREQUAL "tactical_feature_capacity_test")
target_link_libraries("${_test_name}" PRIVATE
highbar_command_queue_test_support)
Expand Down
67 changes: 50 additions & 17 deletions src/circuit/grpc/CommandDispatch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "grpc/FactoryProductionPolicy.h"
#include "grpc/GrpcLog.h"
#include "grpc/StockQueueReader.h"
#include "grpc/StockQueueTrace.h"
#include "grpc/TacticalNativeState.h"

#include "CircuitAI.h"
Expand Down Expand Up @@ -68,7 +69,13 @@ springai::AIFloat3 ToFloat3(const ::highbar::v1::Vector3& v) {
return springai::AIFloat3(v.x(), v.y(), v.z());
}

std::optional<std::vector<NativeQueueEntry>> ReadCurrentTacticalQueue(
struct TacticalQueueRead {
std::vector<NativeQueueEntry> entries;
std::optional<StockQueueReadResult> stock;
bool complete = false;
};

std::optional<TacticalQueueRead> ReadCurrentTacticalQueue(
::circuit::CCircuitAI* ai, springai::Unit* unit,
::highbar::v1::NativeQueueDomain domain) {
if (ai == nullptr || unit == nullptr) return std::nullopt;
Expand All @@ -84,33 +91,35 @@ std::optional<std::vector<NativeQueueEntry>> ReadCurrentTacticalQueue(
[&](const char* data, std::size_t size) {
return lua->CallRules(data, static_cast<int>(size));
});
if (read.status != StockQueueReadStatus::Complete) return std::nullopt;
std::vector<NativeQueueEntry> entries;
entries.reserve(read.entries.size());
TacticalQueueRead result;
result.stock = read;
if (read.status != StockQueueReadStatus::Complete) return result;
result.complete = true;
result.entries.reserve(read.entries.size());
for (const auto& entry : read.entries) {
entries.push_back({0, entry.command_id, entry.options,
result.entries.push_back({0, entry.command_id, entry.options,
entry.tag, 0, entry.params});
}
return entries;
return result;
}
if (domain != ::highbar::v1::NATIVE_QUEUE_DOMAIN_ACTOR_ORDER) {
return std::nullopt;
}
#endif
auto materialize = [](auto& commands)
-> std::optional<std::vector<NativeQueueEntry>> {
auto materialize = [](auto& commands) -> std::optional<TacticalQueueRead> {
if (commands.size() > kMaximumTacticalQueueEntries) {
utils::free_clear(commands);
return std::nullopt;
}
std::vector<NativeQueueEntry> entries;
entries.reserve(commands.size());
TacticalQueueRead result;
result.complete = true;
result.entries.reserve(commands.size());
for (auto* command : commands) {
if (command == nullptr) {
utils::free_clear(commands);
return std::nullopt;
}
entries.push_back({command->GetType(), command->GetId(),
result.entries.push_back({command->GetType(), command->GetId(),
static_cast<std::uint16_t>(command->GetOptions()),
command->GetTag(),
#ifdef HIGHBAR_STOCK_RECOIL
Expand All @@ -121,7 +130,7 @@ std::optional<std::vector<NativeQueueEntry>> ReadCurrentTacticalQueue(
command->GetParams()});
}
utils::free_clear(commands);
return entries;
return result;
};

try {
Expand Down Expand Up @@ -159,6 +168,11 @@ bool DispatchTacticalCommand(::circuit::CCircuitAI* ai,
const ::highbar::v1::NativeTacticalCommand& command,
const FeatureLifetimeLedger* feature_lifetimes,
const StockQueueRevisionContext* stock_queue_context,
StockQueueTraceSink* stock_queue_trace,
const ::highbar::v1::NativeObservationBasis* live_basis,
const StockQueueDispatchIdentity* dispatch_identity,
const std::function<std::uint32_t()>& current_frame,
std::uint32_t perspective_team_id,
const std::function<bool()>& post_control_fence,
TacticalDispatchRefusalReason* refusal_reason) {
auto refuse = [&](TacticalDispatchRefusalReason reason) {
Expand All @@ -173,7 +187,7 @@ bool DispatchTacticalCommand(::circuit::CCircuitAI* ai,
}
auto* native = unit->GetUnit();
const auto current_queue = ReadCurrentTacticalQueue(ai, native, command.queue_domain());
if (!current_queue.has_value()) {
if (!current_queue.has_value() || !current_queue->complete) {
return refuse(TacticalDispatchRefusalReason::kQueueUnavailable);
}
std::optional<std::int32_t> required_tag;
Expand Down Expand Up @@ -204,7 +218,7 @@ bool DispatchTacticalCommand(::circuit::CCircuitAI* ai,
return NativeQueueMatchesExpected(queue,command.expected_queue_revision(),required_tag);
#endif
};
if (!matches_expected(*current_queue)) {
if (!matches_expected(current_queue->entries)) {
return refuse(TacticalDispatchRefusalReason::kQueueChanged);
}
auto friendly = [&](const ::highbar::v1::NativeUnitReference& ref) {
Expand All @@ -225,7 +239,26 @@ bool DispatchTacticalCommand(::circuit::CCircuitAI* ai,
[&] {
const auto final_queue = ReadCurrentTacticalQueue(
ai, native, command.queue_domain());
return final_queue.has_value() && matches_expected(*final_queue);
if (!final_queue.has_value()) return false;
const bool matched = final_queue->complete
&& matches_expected(final_queue->entries);
#ifdef HIGHBAR_STOCK_RECOIL
if (stock_queue_trace != nullptr && stock_queue_context != nullptr
&& live_basis != nullptr && dispatch_identity != nullptr
&& final_queue->stock.has_value()) {
auto identity = *dispatch_identity; identity.matched = matched;
std::uint64_t revision = 0;
if (final_queue->stock->status == StockQueueReadStatus::Complete) {
revision = ComputeStockQueueRevision(
*stock_queue_context, final_queue->stock->entries);
}
stock_queue_trace->Record({true,
current_frame ? current_frame() : 0u, perspective_team_id,
*live_basis, *stock_queue_context, *final_queue->stock,
revision, identity});
}
#endif
return matched;
},
[&] { return !post_control_fence || post_control_fence(); },
std::forward<decltype(effect)>(effect));
Expand Down Expand Up @@ -279,7 +312,7 @@ bool DispatchTacticalCommand(::circuit::CCircuitAI* ai,
: body.queue_policy() == ::highbar::v1::NATIVE_QUEUE_POLICY_REJECT_IF_BUSY
? StockFactoryQueuePolicy::RejectIfBusy
: StockFactoryQueuePolicy::Replace;
if (!StockFactoryProductionPolicyAllows(policy, current_queue->empty())) {
if (!StockFactoryProductionPolicyAllows(policy, current_queue->entries.empty())) {
return false;
}
return dispatch_under_external_control([&] {
Expand All @@ -288,7 +321,7 @@ bool DispatchTacticalCommand(::circuit::CCircuitAI* ai,
});
#else
return DispatchFactoryProductionIfAllowed(
body.queue_policy(), current_queue->empty(), [&] {
body.queue_policy(), current_queue->entries.empty(), [&] {
return dispatch_under_external_control([&] {
unit->CmdBuild(def, native->GetPos(), UNIT_NO_FACING, 0);
});
Expand Down
7 changes: 7 additions & 0 deletions src/circuit/grpc/CommandDispatch.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ class CEnemyInfo;
namespace circuit::grpc {

class FeatureLifetimeLedger;
class StockQueueTraceSink;
struct StockQueueRevisionContext;
struct StockQueueDispatchIdentity;

constexpr int EngineFacingForNativeBuild(
::highbar::v1::NativeBuildFacing facing) {
Expand Down Expand Up @@ -115,6 +117,11 @@ bool DispatchTacticalCommand(::circuit::CCircuitAI* ai,
const ::highbar::v1::NativeTacticalCommand& command,
const FeatureLifetimeLedger* feature_lifetimes,
const StockQueueRevisionContext* stock_queue_context,
StockQueueTraceSink* stock_queue_trace,
const ::highbar::v1::NativeObservationBasis* live_basis,
const StockQueueDispatchIdentity* dispatch_identity,
const std::function<std::uint32_t()>& current_frame,
std::uint32_t perspective_team_id,
const std::function<bool()>& post_control_fence,
TacticalDispatchRefusalReason* refusal_reason = nullptr);

Expand Down
9 changes: 7 additions & 2 deletions src/circuit/grpc/StockQueueReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ StockQueueReadResult ParseStockQueueResponse(const std::string& response,
StockQueueReadResult result;
result.domain = expected_domain;
result.unit_id = expected_unit_id;
result.canonical_request = exact_request;
if (response.empty() || response.size() > kStockQueueMaximumBytes
|| !SevenBitAsciiWithoutNul(response) || response.back() != '\n') return result;
const auto lines = Lines(response);
Expand Down Expand Up @@ -260,10 +261,14 @@ StockQueueReadResult ReadStockQueue(StockQueueDomain domain,std::int32_t unit_id
const auto request = BuildStockQueueRequest(domain, unit_id);
if (request.empty()) return {};
try {
return ParseStockQueueResponse(call_rules(request.data(), request.size()),
auto result = ParseStockQueueResponse(call_rules(request.data(), request.size()),
request, domain, unit_id);
result.domain = domain; result.unit_id = unit_id;
result.canonical_request = request;
return result;
} catch (...) {
return {};
StockQueueReadResult result; result.domain = domain; result.unit_id = unit_id;
result.canonical_request = request; return result;
}
}

Expand Down
1 change: 1 addition & 0 deletions src/circuit/grpc/StockQueueReader.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ struct StockQueueReadResult {
StockQueueDomain domain = StockQueueDomain::Production;
std::int32_t unit_id = -1;
std::string unavailable_reason;
std::string canonical_request;
std::string canonical_response;
std::vector<StockQueueEntry> entries;
};
Expand Down
Loading
Loading