Use global scratch space for temporary objects, better errors
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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."};
|
||||
|
||||
@@ -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
208
kiwi.lua
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user