83 lines
6.8 KiB
Python
83 lines
6.8 KiB
Python
#!/usr/bin/env python3
|
|
"""Execute production bounded radio graph and hop-delivery state machine."""
|
|
from pathlib import Path
|
|
import re, subprocess, tempfile
|
|
ROOT=Path(__file__).resolve().parents[1]
|
|
source=(ROOT/'addons/ai/Scripts/Game/ERR/ERR_HQNetwork.c').read_text()
|
|
def extract(signature):
|
|
a=source.index(signature);b=source.index('{',a);end=b+1;depth=1
|
|
while depth:depth+=(source[end]=='{')-(source[end]=='}');end+=1
|
|
return source[a:end]
|
|
def trans(s):
|
|
s=re.sub(r'//[^\n]*','',s).replace('protected ','').replace('null','nullptr')
|
|
s=s.replace('string.Format','Format').replace('string ', 'std::string ')
|
|
s=s.replace('Math.Round(distance).ToString()', 'std::to_string(int(distance))')
|
|
s=re.sub(r'\b(\w+)\.ToString\(\)',r'std::to_string(\1)',s)
|
|
for name in ['Math','vector']:s=s.replace(name+'.',name+'::')
|
|
for typ in ['IEntity','SCR_AIGroup','ERR_HQRoute','ERR_HQRadioNode','ERR_HQJournal']:
|
|
s=re.sub(r'\b'+typ+r' (\w+)',typ+r'* \1',s)
|
|
s=s.replace('array<int> queue = {}','Arr<int> queue').replace('array<int> cell','Arr<int>* cell').replace('array<int> neighbors','Arr<int>* neighbors')
|
|
s=s.replace('cell = {};','cell = new Arr<int>();')
|
|
s=re.sub(r'foreach\s*\(int (\w+) : neighbors\)',r'for (auto \1 : *neighbors)',s)
|
|
for name in ['carrier','caller','node','route','journal','cell']:
|
|
s=s.replace(name+'.',name+'->')
|
|
s=re.sub(r'(Nodes|m_Nodes)\[([^\]]+)\]\.',r'\1[\2]->',s)
|
|
s=s.replace('Carrier.GetOrigin()', 'Carrier->GetOrigin()')
|
|
s=re.sub(r'\b(int|float|bool) (\w+);',r'\1 \2{};',s)
|
|
return s
|
|
headers=r'''
|
|
#include <cassert>
|
|
#include <cmath>
|
|
#include <algorithm>
|
|
#include <vector>
|
|
#include <map>
|
|
#include <string>
|
|
#include <iostream>
|
|
struct vector{float v[3]{};float operator[](int i)const{return v[i];}static float DistanceXZ(vector a,vector b){return std::hypot(a[0]-b[0],a[2]-b[2]);}};
|
|
vector V(float x,float z=0){return {{x,0,z}};}
|
|
namespace Math{int Floor(float x){return std::floor(x);}}
|
|
std::string Format(const char*,int x,int z){return std::to_string(x)+":"+std::to_string(z);}
|
|
template<class T>struct Arr:std::vector<T>{int Count(){return this->size();}bool IsEmpty(){return this->empty();}void Clear(){this->clear();}void Insert(T x){this->push_back(x);}void InsertAt(T x,int i){this->insert(this->begin()+i,x);}};
|
|
template<class K,class V>struct Map:std::map<K,V>{void Clear(){this->clear();}bool Contains(K k){return this->count(k);}bool Find(K k,V& v){if(!Contains(k))return false;v=this->at(k);return true;}V Get(K k){if(!Contains(k))return {};return this->at(k);}void Insert(K k,V v){(*this)[k]=v;}};
|
|
struct SCR_AIGroup{bool enemy=true;};struct IEntity{vector pos;bool alive=true;int radio=1;bool captured=false;vector GetOrigin(){return pos;}};
|
|
struct ERR_HQRadioNode{IEntity* Carrier{};SCR_AIGroup* Group{};int UnitId{},Capability{};bool Valid(bool manpack=false){return Carrier&&Carrier->alive&&!Carrier->captured&&Carrier->radio>=(manpack?2:1);}std::string Label(){return "unit "+std::to_string(UnitId);}};
|
|
struct ERR_HQJournal{Arr<std::string> events;void Write(std::string kind,std::string text,int){events.Insert(kind+" "+text);}};
|
|
'''
|
|
routefields='Arr<ERR_HQRadioNode*> Nodes;bool Downlink{},Sending{},Failed{};int Cursor{},MessageId{};float Due{},StartedAt=-1;vector SentFrom;std::string Payload;'
|
|
networkfields='Arr<ERR_HQRadioNode*> m_Nodes;Arr<int> m_Parents,m_Depths;Map<IEntity*,int> m_Indices;Map<std::string,Arr<int>*> m_Cells;bool Overflow{};'
|
|
route='struct ERR_HQRoute{'+routefields+'\n'+'\n'.join(trans(extract(x)) for x in ['int Advance(', 'protected string SenderLabel(', 'protected int Fail('])+'};\n'
|
|
network='struct Network{'+networkfields+'\n'+'\n'.join(trans(extract(x)) for x in ['void Clear(', 'protected string Cell(', 'void Add(', 'void Build(', 'ERR_HQRoute Route('])+'};\n'
|
|
tests=r'''
|
|
int main(){
|
|
Network network;SCR_AIGroup group;IEntity c[5];for(int i=0;i<5;i++)c[i].pos=V(i*500);
|
|
c[0].radio=2;for(int i=0;i<5;i++)network.Add(&c[i],&group,37+i,c[i].radio);network.Build();
|
|
assert(network.Route(&c[0])->Nodes.Count()==1);assert(network.Route(&c[3])->Nodes.Count()==4);assert(!network.Route(&c[4]));
|
|
ERR_HQJournal journal;auto up=network.Route(&c[2]);assert(up->Nodes.Count()==3);
|
|
assert(up->Advance(0,&journal)==0);assert(up->Advance(1999,&journal)==0);assert(up->Advance(2000,&journal)==0);
|
|
c[2].alive=false;assert(up->Advance(4000,&journal)==0);c[0].alive=false;assert(up->Advance(6000,&journal)==-1);
|
|
assert(journal.events.back().find("LOST")!=std::string::npos); // final transmission needs its live sender
|
|
for(auto& carrier:c)carrier.alive=true;
|
|
auto good=network.Route(&c[2]);assert(good->Advance(0,&journal)==0);assert(good->Advance(2000,&journal)==0);
|
|
c[2].alive=false;assert(good->Advance(4000,&journal)==0);assert(good->Advance(6000,&journal)==1);
|
|
assert(journal.events.back().find("HQ received")!=std::string::npos); // an already completed earlier hop survives its sender
|
|
auto severed=network.Route(&c[0]);assert(severed->Advance(0,&journal)==0);c[0].radio=0;assert(severed->Advance(2000,&journal)==-1);c[0].radio=2;
|
|
for(auto& carrier:c)carrier.alive=true;
|
|
auto lost=network.Route(&c[2]);assert(lost->Advance(0,&journal)==0);c[1].radio=0;assert(lost->Advance(2000,&journal)==-1);assert(lost->Advance(4000,&journal)==-1);
|
|
c[1].radio=1;auto moving=network.Route(&c[2]);moving->Advance(0,&journal);c[1].pos=V(499);assert(moving->Advance(2000,&journal)==-1);c[1].pos=V(500);
|
|
auto down=network.Route(&c[2],true);assert(down->Nodes[0]->Carrier==&c[0]);assert(down->Advance(0,&journal)==0);
|
|
assert(down->Advance(2000,&journal)==0);assert(down->Advance(4000,&journal)==0);assert(down->Advance(6000,&journal)==1);
|
|
auto captured=network.Route(&c[2],true);captured->Advance(0,&journal);c[0].captured=true;assert(captured->Advance(2000,&journal)==-1);c[0].captured=false;
|
|
auto timeout=network.Route(&c[2]);timeout->Advance(0,&journal);assert(timeout->Advance(30001,&journal)==-1);
|
|
Network disconnected;disconnected.Add(&c[1],&group,1,1);disconnected.Build();assert(!disconnected.Route(&c[1]));
|
|
Network boundary;c[1].pos=V(501);boundary.Add(&c[0],&group,1,2);boundary.Add(&c[1],&group,2,1);boundary.Build();assert(!boundary.Route(&c[1]));
|
|
network.Clear();assert(!network.Route(&c[0]));
|
|
Network crowded;std::vector<IEntity> many(2050);for(auto& carrier:many){carrier.radio=2;crowded.Add(&carrier,&group,1,2);}assert(crowded.Overflow&&crowded.m_Nodes.Count()==2048);
|
|
crowded.Build();assert(crowded.Overflow); // bounded dense graph work
|
|
std::cout<<"PASS: real-carrier shortest-hop paths, 500m boundary, three-hop cap, no-manpack failure, hop timing, completed-hop survival, interrupted final uplink, radio loss, capture, motion, timeout, reset and network bounds\n";
|
|
}
|
|
'''
|
|
with tempfile.TemporaryDirectory(prefix='eai-hq-network-') as tmp:
|
|
cpp=Path(tmp)/'test.cpp';exe=Path(tmp)/'test';cpp.write_text(headers+route+network+tests)
|
|
subprocess.run(['g++','-std=c++17','-Wall','-Wextra',str(cpp),'-o',str(exe)],check=True)
|
|
subprocess.run([str(exe)],check=True)
|