Use global scratch space for temporary objects, better errors

This commit is contained in:
2024-02-14 21:44:46 -06:00
parent bc948d1a61
commit 8b57e0c441
4 changed files with 149 additions and 102 deletions

View File

@@ -7,5 +7,4 @@ insert_final_newline = true
[{*.lua,*.rockspec,.luacov}]
indent_style = space
indent_size = 3
call_parentheses = nosingletable
max_line_length = 98
max_line_length = 105

View File

@@ -117,15 +117,18 @@ const KiwiErr* new_error(const KiwiErr* base, const std::exception& ex) {
static const constexpr KiwiErr kKiwiErrUnhandledCxxException {
KiwiErrUnknown,
"An unhandled C++ exception occurred."};
"An unhandled C++ exception occurred."
};
static const constexpr KiwiErr kKiwiErrNullObjectArg0 {
KiwiErrNullObject,
"null object passed as argument #0 (self)"};
"null object passed as argument #0 (self)"
};
static const constexpr KiwiErr kKiwiErrNullObjectArg1 {
KiwiErrNullObject,
"null object passed as argument #1"};
"null object passed as argument #1"
};
template<typename F>
inline const KiwiErr* wrap_err(F&& f) {
@@ -134,42 +137,49 @@ inline const KiwiErr* wrap_err(F&& f) {
} catch (const UnsatisfiableConstraint& ex) {
static const constexpr KiwiErr err {
KiwiErrUnsatisfiableConstraint,
"The constraint cannot be satisfied."};
"The constraint cannot be satisfied."
};
return &err;
} catch (const UnknownConstraint& ex) {
static const constexpr KiwiErr err {
KiwiErrUnknownConstraint,
"The constraint has not been added to the solver."};
"The constraint has not been added to the solver."
};
return &err;
} catch (const DuplicateConstraint& ex) {
static const constexpr KiwiErr err {
KiwiErrDuplicateConstraint,
"The constraint has already been added to the solver."};
"The constraint has already been added to the solver."
};
return &err;
} catch (const UnknownEditVariable& ex) {
static const constexpr KiwiErr err {
KiwiErrUnknownEditVariable,
"The edit variable has not been added to the solver."};
"The edit variable has not been added to the solver."
};
return &err;
} catch (const DuplicateEditVariable& ex) {
static const constexpr KiwiErr err {
KiwiErrDuplicateEditVariable,
"The edit variable has already been added to the solver."};
"The edit variable has already been added to the solver."
};
return &err;
} catch (const BadRequiredStrength& ex) {
static const constexpr KiwiErr err {
KiwiErrBadRequiredStrength,
"A required strength cannot be used in this context."};
"A required strength cannot be used in this context."
};
return &err;
} catch (const InternalSolverError& ex) {
static const constexpr KiwiErr base {
KiwiErrInternalSolverError,
"An internal solver error occurred."};
"An internal solver error occurred."
};
return new_error(&base, ex);
} catch (std::bad_alloc&) {
static const constexpr KiwiErr err {KiwiErrAlloc, "A memory allocation failed."};

View File

@@ -1,10 +1,10 @@
#ifndef CKIWI_H_
#define CKIWI_H_
#include <stddef.h>
#ifndef KIWI_CKIWI_H_
#define KIWI_CKIWI_H_
#ifdef __cplusplus
extern "C" {
#else
#include <stdbool.h>
#endif
#define KIWI_REF_ISNULL(ref) ((ref).impl_ == NULL)
@@ -98,4 +98,4 @@ char* kiwi_solver_dumps(const KiwiSolver* sp);
// Local Variables:
// mode: c++
// End:
#endif // CKIWI_H_
#endif // KIWI_CKIWI_H_

208
kiwi.lua
View File

@@ -95,7 +95,8 @@ void free(void *);
]])
local strformat = string.format
local ffi_gc, ffi_istype, ffi_new, ffi_string = ffi.gc, ffi.istype, ffi.new, ffi.string
local ffi_copy, ffi_gc, ffi_istype, ffi_new, ffi_string =
ffi.copy, ffi.gc, ffi.istype, ffi.new, ffi.string
local concat = table.concat
local has_table_new, new_tab = pcall(require, "table.new")
@@ -125,26 +126,29 @@ kiwi.ErrKind = ffi.typeof("enum KiwiErrKind") --[[@as kiwi.ErrKind]]
---| '"EQ"' # == (equal)
kiwi.RelOp = ffi.typeof("enum KiwiRelOp")
kiwi.Strength = {
kiwi.strength = {
REQUIRED = 1001001000.0,
STRONG = 1000000.0,
MEDIUM = 1000.0,
WEAK = 1.0,
}
--- Create a custom constraint strength.
---@param a number: Scale factor 1e6
---@param b number: Scale factor 1e3
---@param c number: Scale factor 1
---@param w? number: Weight
---@return number
---@nodiscard
function kiwi.Strength.create(a, b, c, w)
do
local function clamp(n)
return math.max(0, math.min(1000, n))
end
w = w or 1.0
return clamp(a * w) * 1000000.0 + clamp(b * w) * 1000.0 + clamp(c * w)
--- Create a custom constraint strength.
---@param a number: Scale factor 1e6
---@param b number: Scale factor 1e3
---@param c number: Scale factor 1
---@param w? number: Weight
---@return number
---@nodiscard
function kiwi.strength.create(a, b, c, w)
w = w or 1.0
return clamp(a * w) * 1000000.0 + clamp(b * w) * 1000.0 + clamp(c * w)
end
end
local Var = ffi.typeof("struct KiwiVarRefType") --[[@as kiwi.Var]]
@@ -183,8 +187,8 @@ end
---@param var kiwi.Var
---@param coeff number?
---@nodiscard
local function new_expr_one_temp(constant, var, coeff)
local ret = ffi_new(Expression, 1) --[[@as kiwi.Expression]]
local function new_expr_one(constant, var, coeff)
local ret = ffi_gc(ffi_new(Expression, 1), ckiwi.kiwi_expression_del_vars) --[[@as kiwi.Expression]]
local dt = ret.terms_[0]
dt.var = ckiwi.kiwi_var_clone(var)
dt.coefficient = coeff or 1.0
@@ -193,14 +197,6 @@ local function new_expr_one_temp(constant, var, coeff)
return ret
end
---@param constant number
---@param var kiwi.Var
---@param coeff number?
---@nodiscard
local function new_expr_one(constant, var, coeff)
return ffi_gc(new_expr_one_temp(constant, var, coeff), ckiwi.kiwi_expression_del_vars) --[[@as kiwi.Expression]]
end
---@param constant number
---@param var1 kiwi.Var
---@param var2 kiwi.Var
@@ -220,45 +216,78 @@ local function new_expr_pair(constant, var1, var2, coeff1, coeff2)
return ret
end
local Strength = kiwi.Strength
local function typename(o)
if ffi.istype(Var, o) then
return "Var"
elseif ffi.istype(Term, o) then
return "Term"
elseif ffi.istype(Expression, o) then
return "Expression"
elseif ffi.istype(Constraint, o) then
return "Constraint"
else
return type(o)
end
end
local function op_error(a, b, op)
--stylua: ignore
-- level 3 works for arithmetic without TCO (no return), and for rel with TCO forced (explicit return)
error(strformat(
"invalid operand type for '%s' %.40s('%.99s') and %.40s('%.99s')",
op, typename(a), tostring(a), typename(b), tostring(b)), 3)
end
local Strength = kiwi.strength
local REQUIRED = Strength.REQUIRED
local OP_NAMES = {
LE = "<=",
GE = ">=",
EQ = "==",
}
local SIZEOF_TERM = ffi.sizeof(Term) --[[@as integer]]
local tmpexpr = ffi_new(Expression, 2) --[[@as kiwi.Expression]]
local tmpexpr_r = ffi_new(Expression, 1) --[[@as kiwi.Expression]]
local function toexpr(o, temp)
if ffi_istype(Expression, o) then
return o --[[@as kiwi.Expression]]
elseif type(o) == "number" then
temp.constant = o
temp.term_count = 0
return temp
end
temp.constant = 0
temp.term_count = 1
local t = temp.terms_[0]
if ffi_istype(Var, o) then
t.var = o --[[@as kiwi.Var]]
t.coefficient = 1.0
elseif ffi_istype(Term, o) then
ffi_copy(t, o, SIZEOF_TERM)
else
return nil
end
return temp
end
---@param lhs kiwi.Expression|kiwi.Term|kiwi.Var|number
---@param rhs kiwi.Expression|kiwi.Term|kiwi.Var|number
---@param op kiwi.RelOp
---@param strength? number
---@nodiscard
local function rel(lhs, rhs, op, strength)
local function to_expr(o)
if ffi_istype(Expression, o) then
return o --[[@as kiwi.Expression]]
elseif type(o) == "number" then
if o == 0 then
return nil
end
local ret = ffi_new(Expression, 0) --[[@as kiwi.Expression]]
ret.constant = o
ret.term_count = 0
return ret
end
local var
local coeff = 1.0
if ffi_istype(Var, o) then
var = o --[[@as kiwi.Var]]
elseif ffi_istype(Term, o) then
var = o.var
coeff = o.coefficient
else
error("Expected Expression|Term|Var|number, got " .. type(o) .. " instead")
end
return new_expr_one_temp(0.0, var, coeff)
local el = toexpr(lhs, tmpexpr)
local er = toexpr(rhs, tmpexpr_r)
if el == nil or er == nil then
op_error(lhs, rhs, OP_NAMES[op])
end
return ffi_gc(
ckiwi.kiwi_constraint_new(to_expr(lhs), to_expr(rhs), op, strength or REQUIRED),
ckiwi.kiwi_constraint_del
) --[[@as kiwi.Constraint]]
return ffi_gc(ckiwi.kiwi_constraint_new(el, er, op, strength or REQUIRED), ckiwi.kiwi_constraint_del) --[[@as kiwi.Constraint]]
end
--- Define a constraint with expressions as `a <= b`.
@@ -353,11 +382,13 @@ do
elseif type(b) == "number" then
return Term(a, b)
end
error("Invalid var *")
op_error(a, b, "*")
end
function Var_mt.__div(a, b)
assert(type(b) == "number", "Invalid var /")
if type(b) ~= "number" then
op_error(a, b, "/")
end
return Term(a, 1.0 / b)
end
@@ -379,7 +410,7 @@ do
elseif type(b) == "number" then
return new_expr_one(b, a)
end
error("Invalid var +")
op_error(a, b, "+")
end
function Var_mt.__sub(a, b)
@@ -426,10 +457,12 @@ do
local Term_mt = { __index = Term_cls }
local function term_gc(term)
ckiwi.kiwi_var_del(term.var)
end
function Term_mt.__new(T, var, coefficient)
return ffi_gc(ffi_new(T, ckiwi.kiwi_var_clone(var), coefficient or 1.0), function(term)
ckiwi.kiwi_var_del(term.var)
end)
return ffi_gc(ffi_new(T, ckiwi.kiwi_var_clone(var), coefficient or 1.0), term_gc)
end
function Term_mt.__mul(a, b)
@@ -438,11 +471,13 @@ do
elseif type(a) == "number" then
return Term(b.var, b.coefficient * a)
end
error("Invalid term *")
op_error(a, b, "*")
end
function Term_mt.__div(a, b)
assert(type(b) == "number", "Invalid term /")
if type(b) ~= "number" then
op_error(a, b, "/")
end
return Term(a.var, a.coefficient / b)
end
@@ -464,7 +499,7 @@ do
elseif type(b) == "number" then
return new_expr_one(b, a.var, a.coefficient)
end
error("Invalid term + op")
op_error(a, b, "+")
end
function Term_mt.__sub(a, b)
@@ -603,11 +638,13 @@ do
elseif type(b) == "number" then
return mul_expr_constant(a, b)
end
error("Invalid expr *")
op_error(a, b, "*")
end
function Expression_mt.__div(a, b)
assert(type(b) == "number", "Invalid expr /")
if type(b) ~= "number" then
op_error(a, b, "/")
end
return mul_expr_constant(a, 1.0 / b)
end
@@ -629,7 +666,7 @@ do
elseif type(b) == "number" then
return new_expr_constant(a, a.constant + b)
end
error("Invalid expr +")
op_error(a, b, "+")
end
function Expression_mt.__sub(a, b)
@@ -639,7 +676,8 @@ do
function Expression_mt:__tostring()
local tab = new_tab(self.term_count + 1, 0)
for i = 0, self.term_count - 1 do
tab[i + 1] = tostring(self.terms_[i])
local t = self.terms_[i]
tab[i + 1] = tostring(t.coefficient) .. " " .. t.var:name()
end
tab[self.term_count + 1] = self.constant
return concat(tab, " + ")
@@ -693,17 +731,18 @@ do
)
end
local OPS = { [0] = "<=", ">=", "==" }
local STRENGTH_NAMES = {
[Strength.REQUIRED] = "required",
[Strength.STRONG] = "strong",
[Strength.MEDIUM] = "medium",
[Strength.WEAK] = "weak",
}
function Constraint_mt:__tostring()
local ops = { [0] = "<=", ">=", "==" }
local strengths = {
[Strength.REQUIRED] = "required",
[Strength.STRONG] = "strong",
[Strength.MEDIUM] = "medium",
[Strength.WEAK] = "weak",
}
local strength = self:strength()
local strength_str = strengths[strength] or tostring(strength)
local op = ops[tonumber(self:op())]
local strength_str = STRENGTH_NAMES[strength] or tostring(strength)
local op = OPS[tonumber(self:op())]
return strformat("%s %s 0 | %s", tostring(self:expression()), op, strength_str)
end
@@ -726,18 +765,17 @@ do
---@nodiscard
function constraints.pair_ratio(left, coeff, right, constant, op, strength)
assert(ffi_istype(Var, left) and ffi_istype(Var, right))
local lhs = ffi_new(Expression, 2) --[[@as kiwi.Expression]]
local dt = lhs.terms_[0]
local dt = tmpexpr.terms_[0]
dt.var = left
dt.coefficient = 1.0
dt = lhs.terms_[1]
dt = tmpexpr.terms_[1]
dt.var = right
dt.coefficient = -coeff
lhs.constant = -(constant or 0.0)
lhs.term_count = 2
tmpexpr.constant = constant ~= nil and constant or 0
tmpexpr.term_count = 2
return ffi_gc(
ckiwi.kiwi_constraint_new(lhs, nil, op or "EQ", strength or REQUIRED),
ckiwi.kiwi_constraint_new(tmpexpr, nil, op or "EQ", strength or REQUIRED),
ckiwi.kiwi_constraint_del
) --[[@as kiwi.Constraint]]
end
@@ -767,13 +805,13 @@ do
---@nodiscard
function constraints.single(var, constant, op, strength)
assert(ffi_istype(Var, var))
tmpexpr.constant = -(constant or 0)
tmpexpr.term_count = 1
local t = tmpexpr.terms_[0]
t.var = var
t.coefficient = 1.0
return ffi_gc(
ckiwi.kiwi_constraint_new(
new_expr_one_temp(-(constant or 0.0), var, 1.0),
nil,
op or "EQ",
strength or REQUIRED
),
ckiwi.kiwi_constraint_new(tmpexpr, nil, op or "EQ", strength or REQUIRED),
ckiwi.kiwi_constraint_del
) --[[@as kiwi.Constraint]]
end