Files
solvecraft/harness/cpp/shim/geom_attribute.cpp
T
bherbruck 8aa86054d7 geom.attribute: port SmAttribute with difftest
Attributes, behaviours, user lists, the default propagation handlers, colour and
merge-region attributes and ReadIndexedAttributesFromDB, with the SmAObject
attribute-list calls co-ported into AttributeStore. Merge on merge-region
attributes is UNPORTED(topology.shell). difftest: 2029 cases, 1991 pass, 38
deferred, max_rel_err 0.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-12 16:03:08 -04:00

801 lines
28 KiB
C++

// SPDX-License-Identifier: Apache-2.0
// difftest shim for unit `geom.attribute`.
// Wraps upstream SmAttribute and its derived classes (source/SMLib/src/SmAttribute.cpp,
// source/SMLib/inc/SmAttribute.h) together with the SmAObject attribute-list calls
// that drive them (AddAttribute / RemoveAttribute / ClearAttributes / FindAttribute).
//
// sc_geom_attribute_run runs a script against `n_objects` attributed objects (bare
// SmRegion instances, never placed in a B-rep) and a table of attribute "handles"
// (attributes created by the script, MakeCopy results and the placeholders created by
// ReadIndexedAttributesFromDB). Each op is 7 int64s: [op, a, b, c, d, e, f].
// Attribute references (`ref`): ref >= 0 is a handle index; ref < 0 is entry
// (k % 1024) of object (k / 1024)'s attribute list, k = -ref - 1.
//
// Statuses: 0 ok, ST_INVALID (-1) the reference does not resolve, ST_SKIP (-2) the op
// would hit undefined behaviour upstream (the Rust runner applies the same rule to
// its own state), otherwise the upstream SmStatus.
//
// Memory safety rests on the invariant "objects listing an attribute are a sub-multiset
// of its users" (see crates/difftest/src/units/geom_attribute.rs): direct RemoveUser /
// fromClearAttributes run only on attributes no object lists, and `delete` only on
// attributes with no users or standalone attributes whose users all list them. Which
// handles get deleted is predicted from the state before the op (upstream gives no
// way to observe a deletion); everything else in the dump is read back from upstream.
//
// Output (int64 stream, doubles in a second stream):
// per op: status, n_out, out...
// n_handles, alive[n_handles], canon[n_handles] (canonical index or -1)
// n_attrs, per attr: kind, type, id, behavior, n_users, users..., n_vals, vals..., n_dbls
// per object: n, canonical indices...
// n_all, all[n_all] (handle index or -1)
// stream state, stream position (-1 -1 without a stream)
#include "sc_shim.h"
#include <SmApiGeneral.h>
#include <SmAttribute.h>
#include <SmDatabaseIO.h>
#include <SmRegion.h>
#include <cctype>
#include <cstring>
#include <map>
#include <memory>
#include <sstream>
#include <string>
#include <vector>
namespace
{
enum Op : int64_t
{
OP_NEW_PLAIN = 0,
OP_NEW_GENERIC = 1,
OP_NEW_LONG = 2,
OP_NEW_POINTER = 3,
OP_NEW_POINTER_LIST = 4,
OP_NEW_VECTOR3D = 5,
OP_NEW_COLOR = 6,
OP_NEW_TAG = 7,
OP_NEW_MERGE_REGION = 8,
OP_MAKE_COPY = 9,
OP_ADD_ATTRIBUTE = 10,
OP_REMOVE_ATTRIBUTE = 11,
OP_CLEAR_ATTRIBUTES = 12,
OP_SPLIT = 13,
OP_COPY = 14,
OP_MERGE = 15,
OP_COINCIDENT = 16,
OP_EDIT = 17,
OP_DESTRUCTION = 18,
OP_PROPAGATE = 19,
OP_ADD_USER = 20,
OP_REMOVE_USER = 21,
OP_GET_USERS = 22,
OP_QUERY = 23,
OP_MR_QUERY = 24,
OP_MR_SET_REGIONS = 25,
OP_MR_SET_OPERATION = 26,
OP_SET_VECTOR = 27,
OP_DESTROY = 28,
OP_READ_INDEXED = 29,
OP_FIND = 30,
OP_FROM_CLEAR = 31,
OP_LAST = 31,
};
const int64_t ST_INVALID = -1;
const int64_t ST_SKIP = -2;
const int64_t REF_STRIDE = 1024;
const unsigned long READ_CAP = 1ul << 20;
enum Kind : int64_t
{
K_PLAIN = 0,
K_GENERIC = 1,
K_LONG = 2,
K_POINTER = 3,
K_POINTER_LIST = 4,
K_VECTOR3D = 5,
K_MERGE_REGION = 6,
K_TAG = 7,
K_COLOR = 8,
};
const SM_TYPE QUERY_TYPES[11] = {SmObject_TYPE, SmAttribute_TYPE, SmLongAttribute_TYPE,
SmVector3dAttribute_TYPE, SmGenericAttribute_TYPE, SmPointerAttribute_TYPE,
SmPointerListAttribute_TYPE, SmMergeRegionAttribute_TYPE,
SmTagAttribute_TYPE, SmColorAttribute_TYPE, CONT_BASE_TYPE};
struct ShimRegion : public SmRegion
{
ShimRegion() : SmRegion() {}
// SmTopology only offers `new (SmObject*)`; allocate from the context directly.
static void* operator new(size_t size, const SmContext& c) { return SmObject::operator new(size, c); }
static void operator delete(void* p, const SmContext&) { smos_Free(p); }
static void operator delete(void* p) { smos_Free(p); }
};
int64_t kind_of(const SmAttribute* a)
{
switch (a->GetType())
{
case SmAttribute_TYPE:
return K_PLAIN;
case SmGenericAttribute_TYPE:
return K_GENERIC;
case SmLongAttribute_TYPE:
return K_LONG;
case SmPointerAttribute_TYPE:
return a->IsKindOf(SmMergeRegionAttribute_TYPE) ? K_MERGE_REGION : K_POINTER;
case SmPointerListAttribute_TYPE:
return K_POINTER_LIST;
case SmVector3dAttribute_TYPE:
return a->IsKindOf(SmColorAttribute_TYPE) ? K_COLOR : K_VECTOR3D;
case SmTagAttribute_TYPE:
return K_TAG;
default:
return -1;
}
}
bool is_vector(const SmAttribute* a)
{
int64_t k = kind_of(a);
return k == K_VECTOR3D || k == K_COLOR;
}
bool standalone(const SmAttribute* a)
{
return a->GetBehavior() == SM_AB_STANDALONE_COPY || a->GetBehavior() == SM_AB_STANDALONE_REFERENCE;
}
struct Run
{
const SmContext* ctx = nullptr;
std::vector<ShimRegion*> objs;
std::vector<SmAttribute*> handles;
std::vector<bool> alive;
SmTArray<SmAttribute*> all;
std::vector<int64_t> all_handle; // handle index of each placeholder slot (-1 empty)
std::vector<int64_t> pay_i;
std::vector<double> pay_d;
size_t pi = 0, pd = 0;
std::string data;
std::unique_ptr<std::istringstream> iss;
std::unique_ptr<SmDatabaseIO> db;
bool bad_payload = false;
int64_t next_i()
{
if (pi >= pay_i.size())
{
bad_payload = true;
return 0;
}
return pay_i[pi++];
}
double next_d()
{
if (pd >= pay_d.size())
{
bad_payload = true;
return 0.0;
}
return pay_d[pd++];
}
std::vector<SmAttribute*> list(int64_t o) const
{
SmTArray<SmAttribute*> t;
objs[(size_t)o]->GetAttributes(t);
std::vector<SmAttribute*> v;
for (ULONG i = 0; i < t.GetSize(); ++i)
v.push_back(t[i]);
return v;
}
std::vector<SmAObject*> users(const SmAttribute* a) const
{
SmTArray<SmAObject*> t;
a->GetUsers(t);
std::vector<SmAObject*> v;
for (ULONG i = 0; i < t.GetSize(); ++i)
v.push_back(t[i]);
return v;
}
int64_t obj_index(const void* p) const
{
for (size_t i = 0; i < objs.size(); ++i)
if ((const void*)objs[i] == p || (const void*)static_cast<SmAObject*>(objs[i]) == p)
return (int64_t)i;
return -999;
}
// Occurrences of `a` over all object lists.
int64_t listings(const SmAttribute* a) const
{
int64_t n = 0;
for (size_t o = 0; o < objs.size(); ++o)
for (SmAttribute* x : list((int64_t)o))
if (x == a) ++n;
return n;
}
SmAttribute* resolve(int64_t ref) const
{
if (ref >= 0)
{
if ((size_t)ref >= handles.size() || !alive[(size_t)ref]) return nullptr;
return handles[(size_t)ref];
}
int64_t k = -ref - 1;
int64_t o = k / REF_STRIDE, i = k % REF_STRIDE;
if (o < 0 || (size_t)o >= objs.size()) return nullptr;
std::vector<SmAttribute*> l = list(o);
if ((size_t)i >= l.size()) return nullptr;
return l[(size_t)i];
}
bool valid_obj(int64_t o) const { return o >= 0 && (size_t)o < objs.size(); }
void mark_dead(const SmAttribute* a)
{
for (size_t i = 0; i < handles.size(); ++i)
if (alive[i] && handles[i] == a) alive[i] = false;
}
int64_t add_handle(SmAttribute* a)
{
handles.push_back(a);
alive.push_back(true);
return (int64_t)handles.size() - 1;
}
};
bool is_c_space(char c)
{
return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r';
}
int64_t stream_pos(std::istream& in)
{
return (int64_t)in.rdbuf()->pubseekoff(0, std::ios_base::cur, std::ios_base::in);
}
uint64_t load_u64(const std::string& d, size_t at, bool swap)
{
unsigned char b[8];
std::memcpy(b, d.data() + at, 8);
if (swap)
for (int i = 0; i < 4; ++i)
std::swap(b[i], b[7 - i]);
uint64_t v;
std::memcpy(&v, b, 8);
return v;
}
// Whether ReadIndexedAttributesFromDB can run without upstream undefined behaviour.
bool read_is_safe(Run& r)
{
if (!r.db) return false;
std::istream* in = r.db->GetInStreamPtr();
if (in == nullptr || !in->good()) return false;
int64_t pos = stream_pos(*in);
if (pos < 0) return false;
if (r.db->GetFileType() == SM_ASCII)
{
for (size_t i = (size_t)pos; i < r.data.size(); ++i)
if (!is_c_space(r.data[i])) return true;
return false;
}
bool swap = r.db->GetByteSwap() != 0;
size_t p = (size_t)pos;
if (r.data.size() < p + 8) return false;
uint64_t n = load_u64(r.data, p, swap);
if (n > READ_CAP) return false;
if ((r.data.size() - p - 8) / 8 < n) return false;
for (uint64_t i = 0; i < n; ++i)
if (load_u64(r.data, p + 8 + 8 * (size_t)i, swap) > READ_CAP) return false;
return true;
}
// Runs one op; returns its status and appends outputs.
int64_t run_op(Run& r, const int64_t* a, std::vector<int64_t>& out)
{
const SmContext& ctx = *r.ctx;
int64_t op = a[0];
switch (op)
{
case OP_NEW_PLAIN:
out.push_back(r.add_handle(new (ctx) SmAttribute((ULONG)a[1], (SmAttributeBehaviorType)a[2])));
return 0;
case OP_NEW_GENERIC:
{
SmTArray<long> ls;
SmTArray<double> ds;
SmTArray<char> cs;
for (int64_t i = 0; i < a[3]; ++i)
ls.Add((long)r.next_i());
for (int64_t i = 0; i < a[4]; ++i)
ds.Add(r.next_d());
for (int64_t i = 0; i < a[5]; ++i)
cs.Add((char)(unsigned char)r.next_i());
if (r.bad_payload) return SC_ERR_ARG;
out.push_back(
r.add_handle(new (ctx) SmGenericAttribute((ULONG)a[1], (SmAttributeBehaviorType)a[2], ls, ds, cs)));
return 0;
}
case OP_NEW_LONG:
out.push_back(r.add_handle(new (ctx) SmLongAttribute((ULONG)a[1], (long)a[2], (SmAttributeBehaviorType)a[3])));
return 0;
case OP_NEW_POINTER:
out.push_back(r.add_handle(
new (ctx) SmPointerAttribute((ULONG)a[1], (void*)(uintptr_t)a[2], (SmAttributeBehaviorType)a[3])));
return 0;
case OP_NEW_POINTER_LIST:
{
SmTArray<SmObject*> l;
for (int64_t i = 0; i < a[3]; ++i)
l.Add((SmObject*)(uintptr_t)r.next_i());
if (r.bad_payload) return SC_ERR_ARG;
out.push_back(r.add_handle(
new (ctx) SmPointerListAttribute((ULONG)a[1], a[4] != 0 ? nullptr : &l, (SmAttributeBehaviorType)a[2])));
return 0;
}
case OP_NEW_VECTOR3D:
case OP_NEW_COLOR:
{
double x = r.next_d(), y = r.next_d(), z = r.next_d();
if (r.bad_payload) return SC_ERR_ARG;
SmVector3d v(x, y, z);
SmAttribute* at = op == OP_NEW_COLOR
? (SmAttribute*)new (ctx) SmColorAttribute((ULONG)a[1], v, (SmAttributeBehaviorType)a[2])
: (SmAttribute*)new (ctx) SmVector3dAttribute((ULONG)a[1], v, (SmAttributeBehaviorType)a[2]);
out.push_back(r.add_handle(at));
return 0;
}
case OP_NEW_TAG:
{
long t[8];
for (int i = 0; i < 8; ++i)
t[i] = (long)r.next_i();
if (r.bad_payload) return SC_ERR_ARG;
out.push_back(r.add_handle(new (ctx) SmTagAttribute((ULONG)a[1], t[0], t[1], t[2], t[3], t[4], t[5], t[6],
t[7], (SmAttributeBehaviorType)a[2])));
return 0;
}
case OP_NEW_MERGE_REGION:
if (!r.valid_obj(a[1]) || !r.valid_obj(a[3])) return SC_ERR_ARG;
out.push_back(r.add_handle(new (ctx) SmMergeRegionAttribute(r.objs[(size_t)a[1]], (ULONG)a[2],
r.objs[(size_t)a[3]], (ULONG)a[4])));
return 0;
case OP_READ_INDEXED:
{
if (!read_is_safe(r)) return ST_SKIP;
ULONG before = r.all.GetSize();
std::vector<SmAttribute*> old(before);
for (ULONG i = 0; i < before; ++i)
old[i] = r.all[i];
SmTArray<ULONG> idx;
SmStatus st = SmAttributeData::ReadIndexedAttributesFromDB(ctx, r.all, idx, *r.db);
// Placeholders created by this call become handles, in slot order.
r.all_handle.resize(r.all.GetSize(), -1);
for (ULONG i = 0; i < r.all.GetSize(); ++i)
if (r.all[i] != nullptr && (i >= before || old[i] == nullptr)) r.all_handle[i] = r.add_handle(r.all[i]);
if (st != SM_SUCCESS) return st;
for (ULONG i = 0; i < idx.GetSize(); ++i)
out.push_back((int64_t)idx[i]);
return 0;
}
case OP_FIND:
{
if (!r.valid_obj(a[1])) return SC_ERR_ARG;
SmAttribute* f = r.objs[(size_t)a[1]]->FindAttribute((ULONG)a[2]);
std::vector<SmAttribute*> l = r.list(a[1]);
int64_t pos = -1;
for (size_t i = 0; i < l.size() && f != nullptr; ++i)
if (l[i] == f)
{
pos = (int64_t)i;
break;
}
out.push_back(pos);
return 0;
}
case OP_CLEAR_ATTRIBUTES:
{
if (!r.valid_obj(a[1])) return SC_ERR_ARG;
// Predict deletions: per entry, users != 1 -> RemoveUser (never deletes under
// the invariant), users == 1 -> reset and delete unless standalone.
std::map<SmAttribute*, int64_t> count;
for (SmAttribute* x : r.list(a[1]))
{
if (!count.count(x)) count[x] = (int64_t)r.users(x).size();
int64_t& u = count[x];
if (u != 1)
--u;
else
{
u = 0;
if (!standalone(x)) r.mark_dead(x);
}
}
r.objs[(size_t)a[1]]->ClearAttributes();
return 0;
}
default:
break;
}
// Ops on an attribute reference (a[1]).
if (op == OP_ADD_ATTRIBUTE || op == OP_REMOVE_ATTRIBUTE)
{
if (!r.valid_obj(a[1])) return SC_ERR_ARG;
SmAttribute* at = r.resolve(a[2]);
if (at == nullptr) return ST_INVALID;
ShimRegion* o = r.objs[(size_t)a[1]];
if (op == OP_ADD_ATTRIBUTE)
{
o->AddAttribute(at);
return 0;
}
std::vector<SmAttribute*> l = r.list(a[1]);
bool listed = false;
for (SmAttribute* x : l)
listed |= x == at;
if (listed && r.users(at).size() == 1 && a[3] == 0 && !standalone(at)) r.mark_dead(at);
o->RemoveAttribute(at, a[3] != 0 ? TRUE : FALSE);
return 0;
}
SmAttribute* at = r.resolve(a[1]);
if (at == nullptr) return ST_INVALID;
auto obj = [&](int64_t i) -> SmAObject* { return r.objs[(size_t)i]; };
switch (op)
{
case OP_MAKE_COPY:
out.push_back(r.add_handle(at->MakeCopy(ctx)));
return 0;
case OP_SPLIT:
if (!r.valid_obj(a[2]) || !r.valid_obj(a[3]) || !r.valid_obj(a[4])) return SC_ERR_ARG;
at->Split(obj(a[2]), obj(a[3]), obj(a[4]));
return 0;
case OP_COPY:
if (!r.valid_obj(a[2]) || !r.valid_obj(a[3])) return SC_ERR_ARG;
at->Copy(obj(a[2]), obj(a[3]));
return 0;
case OP_MERGE:
{
if (!r.valid_obj(a[2]) || !r.valid_obj(a[3]) || !r.valid_obj(a[4])) return SC_ERR_ARG;
if (kind_of(at) == K_MERGE_REGION)
{
SmAttribute* f[3] = {obj(a[4])->FindAttribute(SM_AI_OTHER_REGION_ID),
obj(a[2])->FindAttribute(SM_AI_OTHER_REGION_ID),
obj(a[3])->FindAttribute(SM_AI_OTHER_REGION_ID)};
for (SmAttribute* x : f)
if (x != nullptr && kind_of(x) != K_MERGE_REGION) return ST_SKIP;
if (f[1] != nullptr && f[2] != nullptr && f[0] == nullptr) return ST_SKIP;
}
at->Merge(obj(a[2]), obj(a[3]), obj(a[4]));
return 0;
}
case OP_COINCIDENT:
if (!r.valid_obj(a[2]) || !r.valid_obj(a[3])) return SC_ERR_ARG;
at->Coincident(obj(a[2]), obj(a[3]), nullptr, nullptr);
return 0;
case OP_EDIT:
if (!r.valid_obj(a[2])) return SC_ERR_ARG;
at->Edit(obj(a[2]));
return 0;
case OP_DESTRUCTION:
if (!r.valid_obj(a[2])) return SC_ERR_ARG;
at->Destruction(obj(a[2]));
return 0;
case OP_PROPAGATE:
{
if (!r.valid_obj(a[2]) || !r.valid_obj(a[3]) || !r.valid_obj(a[4])) return SC_ERR_ARG;
if (kind_of(at) == K_COLOR)
{
SmAttribute* c1 = obj(a[2])->FindAttribute(SM_AI_COLOR);
SmAttribute* c2 = obj(a[3])->FindAttribute(SM_AI_COLOR);
SmAttribute* cm = obj(a[4])->FindAttribute(SM_AI_COLOR);
if (c1 != nullptr && c2 != nullptr && (!is_vector(c1) || !is_vector(c2) || cm == nullptr || !is_vector(cm)))
return ST_SKIP;
}
at->PropagateRegionsThroughBooleans(obj(a[2]), obj(a[3]), obj(a[4]));
return 0;
}
case OP_ADD_USER:
if (!r.valid_obj(a[2])) return SC_ERR_ARG;
at->AddUser(obj(a[2]));
return 0;
case OP_REMOVE_USER:
case OP_FROM_CLEAR:
{
if (!r.valid_obj(a[2])) return SC_ERR_ARG;
if (r.listings(at) > 0) return ST_SKIP;
std::vector<SmAObject*> u = r.users(at);
bool found = false;
for (SmAObject* x : u)
found |= x == obj(a[2]);
if (op == OP_FROM_CLEAR && u.size() == 1)
{
if (!standalone(at)) r.mark_dead(at);
at->fromClearAttributes(obj(a[2]));
return 0;
}
bool dnd = op == OP_REMOVE_USER && a[3] != 0;
if (found && u.size() == 1 && !dnd && !standalone(at)) r.mark_dead(at);
if (op == OP_FROM_CLEAR)
at->fromClearAttributes(obj(a[2]));
else
at->RemoveUser(obj(a[2]), dnd ? TRUE : FALSE);
return 0;
}
case OP_GET_USERS:
for (SmAObject* x : r.users(at))
out.push_back(r.obj_index(x));
return 0;
case OP_QUERY:
{
out.push_back((int64_t)at->GetType());
int64_t mask = 0;
for (int i = 0; i < 11; ++i)
if (at->IsKindOf(QUERY_TYPES[i])) mask |= (int64_t)1 << i;
out.push_back(mask);
out.push_back(at->IsPropagatedThroughBooleans() ? 1 : 0);
out.push_back((int64_t)at->GetAttributeID());
out.push_back((int64_t)at->GetBehavior());
for (const TCHAR* s = at->GetTypeString(); *s; ++s)
out.push_back((int64_t)(unsigned char)*s);
return 0;
}
case OP_MR_QUERY:
{
if (kind_of(at) != K_MERGE_REGION) return ST_SKIP;
SmMergeRegionAttribute* m = (SmMergeRegionAttribute*)at;
out.push_back(m->HasThisInfiniteRegion() ? 1 : 0);
out.push_back(m->HasOtherInfiniteRegion() ? 1 : 0);
out.push_back(m->HasAnySolid((ULONG)a[2]) ? 1 : 0);
out.push_back((int64_t)m->GetOperation());
out.push_back(m->HasAnyOtherPropagatedAttribs() ? 1 : 0);
out.push_back((int64_t)m->GetThisRegions().GetSize());
out.push_back((int64_t)m->GetThisRegionTypes().GetSize());
out.push_back((int64_t)m->GetOtherRegions().GetSize());
if (m->GetThisRegions().GetSize() > 0) out.push_back(r.obj_index(m->GetThisRegion(0)));
return 0;
}
case OP_MR_SET_REGIONS:
{
if (kind_of(at) != K_MERGE_REGION) return ST_SKIP;
if (!r.valid_obj(a[3]) || !r.valid_obj(a[5])) return SC_ERR_ARG;
SmMergeRegionAttribute* m = (SmMergeRegionAttribute*)at;
ULONG idx = (ULONG)a[2];
if (idx < m->GetThisRegions().GetSize() &&
(idx >= m->GetThisRegionTypes().GetSize() || idx >= m->GetOtherRegions().GetSize() ||
idx >= m->GetOtherRegionTypes().GetSize()))
return ST_SKIP;
SmStatus st = m->SetRegions(idx, r.objs[(size_t)a[3]], (ULONG)a[4], r.objs[(size_t)a[5]], (ULONG)a[6]);
return st == SM_SUCCESS ? 0 : st;
}
case OP_MR_SET_OPERATION:
{
if (kind_of(at) != K_MERGE_REGION) return ST_SKIP;
((SmMergeRegionAttribute*)at)->SetOperation((SmBooleanOperationType)a[2]);
return 0;
}
case OP_SET_VECTOR:
{
double x = r.next_d(), y = r.next_d(), z = r.next_d();
if (r.bad_payload) return SC_ERR_ARG;
if (!is_vector(at)) return ST_SKIP;
SmVector3d v(x, y, z);
((SmVector3dAttribute*)at)->SetValue(v);
return 0;
}
case OP_DESTROY:
{
std::vector<SmAObject*> u = r.users(at);
if (!u.empty())
{
if (!standalone(at)) return ST_SKIP;
// users must equal listings (as multisets) so the destructor's loop
// unlinks every listing
std::map<const void*, int64_t> diff;
for (SmAObject* x : u)
diff[x] += 1;
for (size_t o = 0; o < r.objs.size(); ++o)
for (SmAttribute* x : r.list((int64_t)o))
if (x == at) diff[static_cast<SmAObject*>(r.objs[o])] -= 1;
for (auto& kv : diff)
if (kv.second != 0) return ST_SKIP;
}
r.mark_dead(at);
delete at;
return 0;
}
default:
return SC_ERR_ARG;
}
}
void dump(Run& r, std::vector<int64_t>& ints, std::vector<double>& dbls)
{
std::vector<SmAttribute*> canon;
std::map<SmAttribute*, int64_t> index;
auto visit = [&](SmAttribute* a) {
if (index.count(a)) return;
index[a] = (int64_t)canon.size();
canon.push_back(a);
};
for (size_t i = 0; i < r.handles.size(); ++i)
if (r.alive[i]) visit(r.handles[i]);
std::vector<std::vector<SmAttribute*>> lists;
for (size_t o = 0; o < r.objs.size(); ++o)
{
lists.push_back(r.list((int64_t)o));
for (SmAttribute* a : lists.back())
visit(a);
}
ints.push_back((int64_t)r.handles.size());
for (size_t i = 0; i < r.handles.size(); ++i)
ints.push_back(r.alive[i] ? 1 : 0);
for (size_t i = 0; i < r.handles.size(); ++i)
ints.push_back(r.alive[i] ? index[r.handles[i]] : -1);
ints.push_back((int64_t)canon.size());
for (SmAttribute* a : canon)
{
int64_t k = kind_of(a);
ints.push_back(k);
ints.push_back((int64_t)a->GetType());
ints.push_back((int64_t)a->GetAttributeID());
ints.push_back((int64_t)a->GetBehavior());
std::vector<SmAObject*> u = r.users(a);
ints.push_back((int64_t)u.size());
for (SmAObject* x : u)
ints.push_back(r.obj_index(x));
std::vector<int64_t> v;
std::vector<double> d;
switch (k)
{
case K_GENERIC:
{
SmGenericAttribute* g = (SmGenericAttribute*)a;
v.push_back((int64_t)g->GetLongTArray().GetSize());
for (ULONG i = 0; i < g->GetLongTArray().GetSize(); ++i)
v.push_back((int64_t)g->GetLongTArray()[i]);
v.push_back((int64_t)g->GetCharTArray().GetSize());
for (ULONG i = 0; i < g->GetCharTArray().GetSize(); ++i)
v.push_back((int64_t)(unsigned char)g->GetCharTArray()[i]);
for (ULONG i = 0; i < g->GetDoubleTArray().GetSize(); ++i)
d.push_back(g->GetDoubleTArray()[i]);
break;
}
case K_LONG:
v.push_back((int64_t)((SmLongAttribute*)a)->GetValue());
break;
case K_POINTER:
v.push_back((int64_t)(uintptr_t)((SmPointerAttribute*)a)->GetValue());
break;
case K_POINTER_LIST:
{
const SmTArray<SmObject*>& l = ((SmPointerListAttribute*)a)->GetValue();
v.push_back((int64_t)l.GetSize());
for (ULONG i = 0; i < l.GetSize(); ++i)
v.push_back((int64_t)(uintptr_t)l.GetAt(i));
break;
}
case K_VECTOR3D:
case K_COLOR:
{
const SmVector3d& x = ((SmVector3dAttribute*)a)->GetValue();
d.push_back(x.x);
d.push_back(x.y);
d.push_back(x.z);
break;
}
case K_MERGE_REGION:
{
SmMergeRegionAttribute* m = (SmMergeRegionAttribute*)a;
v.push_back((int64_t)m->GetOperation());
v.push_back((int64_t)m->GetThisRegions().GetSize());
for (ULONG i = 0; i < m->GetThisRegions().GetSize(); ++i)
v.push_back(r.obj_index(m->GetThisRegions()[i]));
v.push_back((int64_t)m->GetThisRegionTypes().GetSize());
for (ULONG i = 0; i < m->GetThisRegionTypes().GetSize(); ++i)
v.push_back((int64_t)m->GetThisRegionTypes()[i]);
v.push_back((int64_t)m->GetOtherRegions().GetSize());
for (ULONG i = 0; i < m->GetOtherRegions().GetSize(); ++i)
v.push_back(r.obj_index(m->GetOtherRegions()[i]));
v.push_back((int64_t)m->GetOtherRegionTypes().GetSize());
for (ULONG i = 0; i < m->GetOtherRegionTypes().GetSize(); ++i)
v.push_back((int64_t)m->GetOtherRegionTypes()[i]);
break;
}
case K_TAG:
{
SmTagAttribute* t = (SmTagAttribute*)a;
long f[8] = {t->m_lPrimaryID, t->m_lPrimaryOrdinal, t->m_lSecondaryIDType, t->m_lSecondaryID,
t->m_lSecondaryOrdinal, t->m_lThirdIDType, t->m_lThirdID, t->m_lThirdOrdinal};
for (long x : f)
v.push_back((int64_t)x);
break;
}
default:
break;
}
ints.push_back((int64_t)v.size());
ints.insert(ints.end(), v.begin(), v.end());
ints.push_back((int64_t)d.size());
dbls.insert(dbls.end(), d.begin(), d.end());
}
for (auto& l : lists)
{
ints.push_back((int64_t)l.size());
for (SmAttribute* a : l)
ints.push_back(index[a]);
}
ints.push_back((int64_t)r.all.GetSize());
for (ULONG i = 0; i < r.all.GetSize(); ++i)
ints.push_back(i < r.all_handle.size() ? r.all_handle[i] : -1);
std::istream* in = r.db ? r.db->GetInStreamPtr() : nullptr;
ints.push_back(in ? (int64_t)in->rdstate() : -1);
ints.push_back(in ? stream_pos(*in) : -1);
}
} // namespace
// n_objects: attributed objects; ops[7*n_ops]; payload_i / payload_d: creation data
// consumed in order; data/file_type/byte_swap: the stream of OP_READ_INDEXED
// (file_type < 0: no stream).
SC_EXPORT int32_t sc_geom_attribute_run(int64_t n_objects, const int64_t* ops, int64_t n_ops,
const int64_t* payload_i, int64_t n_payload_i, const double* payload_d,
int64_t n_payload_d, const uint8_t* data, int64_t data_len,
int32_t file_type, int32_t byte_swap, int64_t* ints, int64_t ints_cap,
int64_t* ints_len, double* dbls, int64_t dbls_cap, int64_t* dbls_len)
{
if (n_objects < 0 || n_objects > 64 || n_ops < 0 || n_payload_i < 0 || n_payload_d < 0 || data_len < 0 ||
file_type > 3)
return SC_ERR_ARG;
SC_TRY
Run r;
r.ctx = SmApiGetOrCreateContext();
if (r.ctx == nullptr) return SC_ERR_ARG;
for (int64_t i = 0; i < n_objects; ++i)
r.objs.push_back(new (*r.ctx) ShimRegion());
r.pay_i.assign(payload_i, payload_i + n_payload_i);
r.pay_d.assign(payload_d, payload_d + n_payload_d);
if (file_type >= 0)
{
r.data.assign((const char*)data, (size_t)data_len);
r.iss.reset(new std::istringstream(r.data));
r.db.reset(new SmDatabaseIOStream((SmFileType)file_type, r.iss.get()));
r.db->SetByteSwap(byte_swap != 0 ? TRUE : FALSE);
}
std::vector<int64_t> o_ints;
std::vector<double> o_dbls;
for (int64_t k = 0; k < n_ops; ++k)
{
const int64_t* a = ops + 7 * k;
if (a[0] < 0 || a[0] > OP_LAST) return SC_ERR_ARG;
std::vector<int64_t> out;
int64_t st = run_op(r, a, out);
if (st == SC_ERR_ARG) return SC_ERR_ARG;
o_ints.push_back(st);
o_ints.push_back((int64_t)out.size());
o_ints.insert(o_ints.end(), out.begin(), out.end());
}
dump(r, o_ints, o_dbls);
// Objects and surviving attributes are leaked on purpose: deleting a region runs
// its Notify/ClearAttributes chain, which is not what is being compared.
*ints_len = (int64_t)o_ints.size();
*dbls_len = (int64_t)o_dbls.size();
if (*ints_len > ints_cap || *dbls_len > dbls_cap) return SC_ERR_BUFFER;
for (size_t i = 0; i < o_ints.size(); ++i)
ints[i] = o_ints[i];
for (size_t i = 0; i < o_dbls.size(); ++i)
dbls[i] = o_dbls[i];
return SC_OK;
SC_CATCH
}