Compare commits
28 Commits
08e9bf08e7
...
old-master
| Author | SHA1 | Date | |
|---|---|---|---|
| f6bf09a3eb | |||
| 579d671a77 | |||
| 61ba76c5a3 | |||
| 8854c0edbe | |||
| 00a9fda814 | |||
| a8c1a10cab | |||
| 2a71914ed8 | |||
| dea448e46b | |||
| 3811d82212 | |||
| 2baee14c5d | |||
| 198265ee36 | |||
| 604e3df41f | |||
| 6a99504835 | |||
| 9b00e62d43 | |||
| 3631704544 | |||
| 17f67b8879 | |||
| d83cc3468c | |||
| b3cf6136a4 | |||
| 2ffc5a333b | |||
| d85796a038 | |||
| 37833f7b2b | |||
| 59465d3142 | |||
| 8b57e0c441 | |||
| bc948d1a61 | |||
| 3311b582a1 | |||
| 84a01179cd | |||
| e43272487f | |||
| 59bdeedc18 |
@@ -27,7 +27,7 @@ BreakBeforeTernaryOperators: true
|
||||
BreakConstructorInitializers: AfterColon
|
||||
BreakInheritanceList: AfterColon
|
||||
BreakStringLiterals: false
|
||||
ColumnLimit: 90
|
||||
ColumnLimit: 98
|
||||
CompactNamespaces: false
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: true
|
||||
ConstructorInitializerIndentWidth: 4
|
||||
@@ -65,7 +65,7 @@ PointerAlignment: Left
|
||||
ReferenceAlignment: Left # New in v13. int &name ==> int& name
|
||||
ReflowComments: false
|
||||
SeparateDefinitionBlocks: Always # New in v14.
|
||||
SortIncludes: true
|
||||
SortIncludes: false
|
||||
SortUsingDeclarations: true
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceAfterLogicalNot: false
|
||||
@@ -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
|
||||
|
||||
36
.github/workflows/busted.yml
vendored
Normal file
36
.github/workflows/busted.yml
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
name: Busted
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
busted:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
lua_version: ["luajit-openresty", "luajit-2.1.0-beta3", "luajit-git"]
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup ‘lua’
|
||||
uses: jkl1337/gh-actions-lua@master
|
||||
with:
|
||||
luaVersion: ${{ matrix.lua_version }}
|
||||
- name: Setup ‘luarocks’
|
||||
uses: jkl1337/gh-actions-luarocks@master
|
||||
- name: Setup dependencies
|
||||
run: |
|
||||
luarocks install busted
|
||||
luarocks install luacov-coveralls
|
||||
- name: Build C library
|
||||
run: make
|
||||
- name: Run busted tests
|
||||
run: busted -c -v
|
||||
- name: Report test coverage
|
||||
if: success()
|
||||
continue-on-error: true
|
||||
run: luacov-coveralls -e .luarocks -e spec
|
||||
env:
|
||||
COVERALLS_REPO_TOKEN: ${{ github.token }}
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -2,6 +2,9 @@
|
||||
/lua
|
||||
/lua_modules
|
||||
/.luarocks
|
||||
*.pch
|
||||
*.gch
|
||||
*.so
|
||||
*.o
|
||||
.cache/
|
||||
compile_commands.json
|
||||
|
||||
@@ -8,6 +8,6 @@
|
||||
"lua_modules/share/lua/5.1/?.lua",
|
||||
"lua_modules/share/lua/5.1/?/init.lua"
|
||||
],
|
||||
"workspace.library": ["lua_modules/share/lua/5.1"],
|
||||
"workspace.library": ["${3rd}/busted/library", "${3rd}/luassert/library"],
|
||||
"workspace.checkThirdParty": false
|
||||
}
|
||||
|
||||
7
LICENSE
Normal file
7
LICENSE
Normal file
@@ -0,0 +1,7 @@
|
||||
Copyright 2024 John Luebs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
85
Makefile
Normal file
85
Makefile
Normal file
@@ -0,0 +1,85 @@
|
||||
CC := $(CROSS)gcc
|
||||
CP := cp
|
||||
RM := rm
|
||||
LIBFLAG := -shared
|
||||
LIB_EXT := so
|
||||
LUA_INCDIR := /usr/include
|
||||
|
||||
SRCDIR := .
|
||||
|
||||
OPTFLAG := -O2
|
||||
CCFLAGS += $(OPTFLAG) -fPIC -Wall -fstrict-flex-arrays -fvisibility=hidden -Wformat=2 -Wconversion -Wimplicit-fallthrough
|
||||
|
||||
SANITIZE_FLAGS := -fsanitize=undefined -fsanitize=address
|
||||
LTO_FLAGS := -flto=auto
|
||||
|
||||
ifdef SANITIZE
|
||||
CCFLAGS += $(SANITIZE_FLAGS)
|
||||
endif
|
||||
ifdef LTO
|
||||
CCFLAGS += $(LTO_FLAGS)
|
||||
endif
|
||||
|
||||
override CPPFLAGS += -I$(SRCDIR) -I$(SRCDIR)/kiwi -I$(LUA_INCDIR)
|
||||
override CXXFLAGS += -std=c++14 -fno-rtti $(CCFLAGS)
|
||||
override CFLAGS += -std=c99 $(CCFLAGS)
|
||||
|
||||
ifneq ($(filter %gcc,$(CC)),)
|
||||
CXX := $(patsubst %gcc,%g++,$(CC))
|
||||
PCH := ljkiwi.hpp.gch
|
||||
else
|
||||
ifneq ($(filter %clang,$(CC)),)
|
||||
CXX := $(patsubst %clang,%clang++,$(CC))
|
||||
override CXXFLAGS += -pedantic -Wno-c99-extensions
|
||||
PCH := ljkiwi.hpp.pch
|
||||
endif
|
||||
endif
|
||||
|
||||
ifdef LUA
|
||||
LUA_VERSION ?= $(lastword $(shell $(LUA) -e "print(_VERSION)"))
|
||||
endif
|
||||
|
||||
ifndef LUA_VERSION
|
||||
LJKIWI_CKIWI := 1
|
||||
else
|
||||
ifneq ($(LUA_VERSION),5.1)
|
||||
LJKIWI_CKIWI :=
|
||||
endif
|
||||
endif
|
||||
|
||||
OBJS := luakiwi.o
|
||||
ifdef LJKIWI_CKIWI
|
||||
OBJS += ckiwi.o
|
||||
endif
|
||||
|
||||
vpath %.cpp $(SRCDIR)/ckiwi $(SRCDIR)/luakiwi
|
||||
vpath %.h $(SRCDIR)/ckiwi $(SRCDIR)/luakiwi
|
||||
|
||||
all: ljkiwi.$(LIB_EXT)
|
||||
|
||||
install:
|
||||
$(CP) -f ljkiwi.$(LIB_EXT) $(INST_LIBDIR)/ljkiwi.$(LIB_EXT)
|
||||
$(CP) -f kiwi.lua $(INST_LUADIR)/kiwi.lua
|
||||
|
||||
clean:
|
||||
$(RM) -f ljkiwi.$(LIB_EXT) $(OBJS) $(PCH)
|
||||
|
||||
ckiwi.o: $(PCH) ckiwi.cpp ckiwi.h
|
||||
luakiwi.o: $(PCH) luakiwi-int.h luacompat.h
|
||||
|
||||
ljkiwi.$(LIB_EXT): $(OBJS)
|
||||
$(CXX) $(CCFLAGS) $(LIBFLAG) -o $@ $(OBJS)
|
||||
|
||||
%.hpp.gch: %.hpp
|
||||
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -x c++-header -o $@ $<
|
||||
|
||||
%.hpp.pch: %.hpp
|
||||
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -x c++-header -o $@ $<
|
||||
|
||||
%.o: %.c
|
||||
$(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
%.o: %.cpp
|
||||
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $<
|
||||
|
||||
.PHONY: all install clean
|
||||
94
README.md
Normal file
94
README.md
Normal file
@@ -0,0 +1,94 @@
|
||||
ljkiwi - Free LuaJIT FFI kiwi (Cassowary derived) constraint solver.
|
||||
|
||||
[](https://github.com/jkl1337/ljkiwi/actions/workflows/busted.yml)
|
||||
[](https://coveralls.io/github/jkl1337/ljkiwi?branch=master)
|
||||
[](https://luarocks.org/modules/jkl/ljkiwi)
|
||||
|
||||
# Introduction
|
||||
|
||||
Kiwi is a reasonably efficient C++ implementation of the Cassowary constraint solving algorithm. It is an implementation of the algorithm as described in the paper ["The Cassowary Linear Arithmetic Constraint Solving Algorithm"](http://www.cs.washington.edu/research/constraints/cassowary/techreports/cassowaryTR.pdf) by Greg J. Badros and Alan Borning. The Kiwi implementation is not based on the original C++ implementation, but is a ground-up reimplementation with performance 10x to 500x faster in typical use.
|
||||
Cassowary constraint solving is a technique that is particularly well suited to user interface layout. It is the algorithm Apple uses for iOS and OS X Auto Layout.
|
||||
|
||||
There are a few Lua implementations or attempts. The SILE typesetting system has a pure Lua implementation of the original Cassowary code, which appears to be correct but is quite slow. There are two extant Lua ports of Kiwi, one that is based on a C rewrite of Kiwi. However testing of these was not encouraging with either segfaults or incorrect results.
|
||||
Since the C++ Kiwi library is well tested and widely used it was simpler to provide a LuaJIT FFI wrapper and use that.
|
||||
This package has no dependencies other than a C++14 toolchain to compile the included Kiwi library and a small C wrapper.
|
||||
|
||||
The Lua API has a pure Lua expression builder. There is of course some overhead to this, however in most cases expression building is infrequent and the underlying structures can be reused.
|
||||
|
||||
The wrapper is quite close to the Kiwi C++/Python port with a few naming changes.
|
||||
|
||||
## Example
|
||||
|
||||
```lua
|
||||
local kiwi = require("kiwi")
|
||||
local Var = kiwi.Var
|
||||
|
||||
local Button = setmetatable({}, {
|
||||
__call = function(_, identifier)
|
||||
return setmetatable({
|
||||
left = Var(identifier .. " left"),
|
||||
width = Var(identifier .. " width"),
|
||||
}, {
|
||||
__tostring = function(self)
|
||||
return "Button(" .. self.left:value() .. ", " .. self.width:value() .. ")"
|
||||
end,
|
||||
})
|
||||
end,
|
||||
})
|
||||
|
||||
local b1 = Button("b1")
|
||||
local b2 = Button("b2")
|
||||
|
||||
local left_edge = Var("left")
|
||||
local right_edge = Var("width")
|
||||
|
||||
local STRONG = kiwi.Strength.STRONG
|
||||
|
||||
-- stylua: ignore start
|
||||
local constraints = {
|
||||
left_edge :eq(0.0),
|
||||
-- two buttons are the same width
|
||||
b1.width :eq(b2.width),
|
||||
-- button1 starts 50 from the left margin
|
||||
b1.left :eq(left_edge + 50),
|
||||
-- button2 ends 50 from the right margin
|
||||
right_edge :eq(b2.left + b2.width + 50),
|
||||
-- button2 starts at least 100 from the end of button1. This is the "elastic" constraint
|
||||
b2.left :ge(b1.left + b1.width + 100),
|
||||
-- button1 has a minimum width of 87
|
||||
b1.width :ge(87),
|
||||
-- button1 has a preferred width of 87
|
||||
b1.width :eq(87, STRONG),
|
||||
-- button2 has minimum width of 113
|
||||
b2.width :ge(113),
|
||||
-- button2 has a preferred width of 113
|
||||
b2.width :eq(113, STRONG),
|
||||
}
|
||||
-- stylua: ignore end
|
||||
|
||||
local solver = kiwi.Solver()
|
||||
|
||||
for _, c in ipairs(constraints) do
|
||||
solver:add_constraint(c)
|
||||
end
|
||||
|
||||
solver:update_vars()
|
||||
|
||||
print(b1) -- Button(50, 113)
|
||||
print(b2) -- Button(263, 113)
|
||||
print(left_edge:value()) -- 0
|
||||
print(right_edge:value()) -- 426
|
||||
|
||||
solver:add_edit_var(right_edge, STRONG)
|
||||
solver:suggest_value(right_edge, 500)
|
||||
solver:update_vars()
|
||||
print(b1) -- Button(50, 113)
|
||||
print(b2) -- Button(337, 113)
|
||||
print(right_edge:value()) -- 500
|
||||
|
||||
```
|
||||
|
||||
In addition to the expression builder there is a convenience constraints submodule with: `pair_ratio`, `pair`, and `single` to allow efficient construction of the most common simple expression types for GUI layout.
|
||||
|
||||
## Documentation
|
||||
WIP - However the API is fully annotated and will work with lua-language-server. Documentation can also be generated with lua-language-server.
|
||||
2
build.bat
Normal file
2
build.bat
Normal file
@@ -0,0 +1,2 @@
|
||||
cl /nologo /O2 /W4 /wd4200 /c /D_CRT_SECURE_NO_DEPRECATE /I "C:\Program Files\luarocks\include" /EHs /I kiwi luakiwi\luakiwi.cpp
|
||||
link /DLL /out:ckiwi.dll /def:luakiwi.def /LIBPATH:"C:\Program Files\luarocks\lib" luakiwi.obj lua54.lib
|
||||
356
ckiwi/ckiwi.cpp
Normal file
356
ckiwi/ckiwi.cpp
Normal file
@@ -0,0 +1,356 @@
|
||||
#include "ljkiwi.hpp"
|
||||
#include "ckiwi.h"
|
||||
|
||||
#include <kiwi/kiwi.h>
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
#if defined(__GNUC__) && !defined(LJKIWI_NO_BUILTIN)
|
||||
#define lk_likely(x) (__builtin_expect(((x) != 0), 1))
|
||||
#define lk_unlikely(x) (__builtin_expect(((x) != 0), 0))
|
||||
#else
|
||||
#define lk_likely(x) (x)
|
||||
#define lk_unlikely(x) (x)
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace kiwi;
|
||||
|
||||
const KiwiErr* new_error(const KiwiErr* base, const std::exception& ex) {
|
||||
if (!std::strcmp(ex.what(), base->message))
|
||||
return base;
|
||||
|
||||
const auto msg_n = std::strlen(ex.what()) + 1;
|
||||
|
||||
auto* mem = static_cast<char*>(std::malloc(sizeof(KiwiErr) + msg_n));
|
||||
if (!mem) {
|
||||
return base;
|
||||
}
|
||||
|
||||
const auto* err = new (mem) KiwiErr {base->kind, mem + sizeof(KiwiErr), true};
|
||||
std::memcpy(const_cast<char*>(err->message), ex.what(), msg_n);
|
||||
return err;
|
||||
}
|
||||
|
||||
static const constexpr KiwiErr kKiwiErrUnhandledCxxException {
|
||||
KiwiErrUnknown,
|
||||
"An unhandled C++ exception occurred."};
|
||||
|
||||
static const constexpr KiwiErr kKiwiErrNullObjectArg0 {
|
||||
KiwiErrNullObject,
|
||||
"null object passed as argument #0 (self)"};
|
||||
|
||||
static const constexpr KiwiErr kKiwiErrNullObjectArg1 {
|
||||
KiwiErrNullObject,
|
||||
"null object passed as argument #1"};
|
||||
|
||||
template<typename F>
|
||||
inline const KiwiErr* wrap_err(F&& f) {
|
||||
try {
|
||||
f();
|
||||
} catch (const UnsatisfiableConstraint& ex) {
|
||||
static const constexpr KiwiErr err {
|
||||
KiwiErrUnsatisfiableConstraint,
|
||||
"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."};
|
||||
return &err;
|
||||
|
||||
} catch (const DuplicateConstraint& ex) {
|
||||
static const constexpr KiwiErr err {
|
||||
KiwiErrDuplicateConstraint,
|
||||
"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."};
|
||||
return &err;
|
||||
|
||||
} catch (const DuplicateEditVariable& ex) {
|
||||
static const constexpr KiwiErr err {
|
||||
KiwiErrDuplicateEditVariable,
|
||||
"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."};
|
||||
return &err;
|
||||
|
||||
} catch (const InternalSolverError& ex) {
|
||||
static const constexpr KiwiErr base {
|
||||
KiwiErrInternalSolverError,
|
||||
"An internal solver error occurred."};
|
||||
return new_error(&base, ex);
|
||||
} catch (std::bad_alloc&) {
|
||||
static const constexpr KiwiErr err {KiwiErrAlloc, "A memory allocation failed."};
|
||||
return &err;
|
||||
} catch (const std::exception& ex) {
|
||||
return new_error(&kKiwiErrUnhandledCxxException, ex);
|
||||
} catch (...) {
|
||||
return &kKiwiErrUnhandledCxxException;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template<typename P, typename R, typename F>
|
||||
inline const KiwiErr* wrap_err(P self, F&& f) {
|
||||
if (lk_unlikely(!self)) {
|
||||
return &kKiwiErrNullObjectArg0;
|
||||
}
|
||||
return wrap_err([&]() { f(self->solver); });
|
||||
}
|
||||
|
||||
template<typename P, typename R, typename F>
|
||||
inline const KiwiErr* wrap_err(P self, R item, F&& f) {
|
||||
if (lk_unlikely(!self)) {
|
||||
return &kKiwiErrNullObjectArg0;
|
||||
} else if (lk_unlikely(!item)) {
|
||||
return &kKiwiErrNullObjectArg1;
|
||||
}
|
||||
return wrap_err([&]() { f(self->solver, *item); });
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
extern "C" {
|
||||
|
||||
KiwiTypeInfo kiwi_ti_KiwiVar = {sizeof(KiwiVar), alignof(KiwiVar)};
|
||||
KiwiTypeInfo kiwi_ti_KiwiConstraint = {sizeof(KiwiConstraint), alignof(KiwiConstraint)};
|
||||
|
||||
void kiwi_var_construct(const char* name, void* mem) {
|
||||
new (mem) KiwiVar {lk_likely(name) ? name : ""};
|
||||
}
|
||||
|
||||
void kiwi_var_release(KiwiVar* var) {
|
||||
if (lk_likely(var))
|
||||
var->~KiwiVar();
|
||||
}
|
||||
|
||||
void kiwi_var_retain(KiwiVar* var) {
|
||||
if (lk_likely(var)) {
|
||||
alignas(KiwiVar) unsigned char buf[sizeof(KiwiVar)];
|
||||
new (buf) KiwiVar(*var);
|
||||
}
|
||||
}
|
||||
|
||||
const char* kiwi_var_name(const KiwiVar* var) {
|
||||
return lk_likely(var) ? var->name().c_str() : "(<null>)";
|
||||
}
|
||||
|
||||
void kiwi_var_set_name(KiwiVar* var, const char* name) {
|
||||
if (lk_likely(var))
|
||||
var->setName(name);
|
||||
}
|
||||
|
||||
double kiwi_var_value(const KiwiVar* var) {
|
||||
return lk_likely(var) ? var->value() : std::numeric_limits<double>::quiet_NaN();
|
||||
}
|
||||
|
||||
void kiwi_var_set_value(KiwiVar* var, double value) {
|
||||
if (lk_likely(var))
|
||||
var->setValue(value);
|
||||
}
|
||||
|
||||
bool kiwi_var_eq(const KiwiVar* var, const KiwiVar* other) {
|
||||
return lk_likely(var && other) && var->equals(*other);
|
||||
}
|
||||
|
||||
void kiwi_expression_retain(KiwiExpression* expr) {
|
||||
if (lk_unlikely(!expr))
|
||||
return;
|
||||
alignas(KiwiVar) unsigned char buf[sizeof(KiwiVar)];
|
||||
for (auto* t = expr->terms_; t != expr->terms_ + expr->term_count; ++t) {
|
||||
new (buf) KiwiVar(*t->var);
|
||||
}
|
||||
}
|
||||
|
||||
void kiwi_expression_destroy(KiwiExpression* expr) {
|
||||
if (lk_unlikely(!expr))
|
||||
return;
|
||||
|
||||
if (expr->owner) {
|
||||
expr->owner->~KiwiConstraint();
|
||||
} else {
|
||||
for (auto* t = expr->terms_; t != expr->terms_ + expr->term_count; ++t) {
|
||||
t->var->~KiwiVar();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void kiwi_constraint_construct(
|
||||
const KiwiExpression* lhs,
|
||||
const KiwiExpression* rhs,
|
||||
enum KiwiRelOp op,
|
||||
double strength,
|
||||
void* mem
|
||||
) {
|
||||
if (strength < 0.0) {
|
||||
strength = kiwi::strength::required;
|
||||
}
|
||||
|
||||
std::vector<Term> terms;
|
||||
terms.reserve((lhs ? lhs->term_count : 0) + (rhs ? rhs->term_count : 0));
|
||||
|
||||
if (lhs) {
|
||||
for (auto* t = lhs->terms_; t != lhs->terms_ + lhs->term_count; ++t) {
|
||||
if (t->var)
|
||||
terms.emplace_back(*t->var, t->coefficient);
|
||||
}
|
||||
}
|
||||
if (rhs) {
|
||||
for (auto* t = rhs->terms_; t != rhs->terms_ + rhs->term_count; ++t) {
|
||||
if (t->var)
|
||||
terms.emplace_back(*t->var, -t->coefficient);
|
||||
}
|
||||
}
|
||||
|
||||
new (mem) Constraint(
|
||||
Expression(std::move(terms), (lhs ? lhs->constant : 0.0) - (rhs ? rhs->constant : 0.0)),
|
||||
static_cast<RelationalOperator>(op),
|
||||
strength
|
||||
);
|
||||
}
|
||||
|
||||
void kiwi_constraint_release(KiwiConstraint* c) {
|
||||
if (lk_likely(c))
|
||||
c->~KiwiConstraint();
|
||||
}
|
||||
|
||||
void kiwi_constraint_retain(KiwiConstraint* c) {
|
||||
if (lk_likely(c)) {
|
||||
alignas(KiwiConstraint) unsigned char buf[sizeof(KiwiConstraint)];
|
||||
new (buf) KiwiConstraint(*c);
|
||||
}
|
||||
}
|
||||
|
||||
double kiwi_constraint_strength(const KiwiConstraint* c) {
|
||||
return lk_likely(c) ? c->strength() : std::numeric_limits<double>::quiet_NaN();
|
||||
}
|
||||
|
||||
enum KiwiRelOp kiwi_constraint_op(const KiwiConstraint* c) {
|
||||
return lk_likely(c) ? static_cast<KiwiRelOp>(c->op()) : KiwiRelOp::KIWI_OP_EQ;
|
||||
}
|
||||
|
||||
bool kiwi_constraint_violated(const KiwiConstraint* c) {
|
||||
return lk_likely(c) ? c->violated() : false;
|
||||
}
|
||||
|
||||
int kiwi_constraint_expression(KiwiConstraint* c, KiwiExpression* out, int out_size) {
|
||||
if (lk_unlikely(!c))
|
||||
return 0;
|
||||
|
||||
const auto& expr = c->expression();
|
||||
const auto& terms = expr.terms();
|
||||
const auto term_count = static_cast<int>(terms.size());
|
||||
if (!out || out_size < term_count)
|
||||
return term_count;
|
||||
|
||||
for (int i = 0; i < term_count; ++i) {
|
||||
out->terms_[i].var = const_cast<KiwiVar*>(&terms[i].variable());
|
||||
out->terms_[i].coefficient = terms[i].coefficient();
|
||||
}
|
||||
out->constant = expr.constant();
|
||||
out->term_count = term_count;
|
||||
out->owner = c;
|
||||
kiwi_constraint_retain(c);
|
||||
|
||||
return term_count;
|
||||
}
|
||||
|
||||
struct KiwiSolver {
|
||||
unsigned error_mask;
|
||||
Solver solver;
|
||||
};
|
||||
|
||||
KiwiTypeInfo kiwi_ti_KiwiSolver = {sizeof(KiwiSolver), alignof(KiwiSolver)};
|
||||
|
||||
void kiwi_solver_construct(unsigned error_mask, void* mem) {
|
||||
new (mem) KiwiSolver {error_mask};
|
||||
}
|
||||
|
||||
void kiwi_solver_destroy(KiwiSolver* s) {
|
||||
if (lk_likely(s))
|
||||
s->~KiwiSolver();
|
||||
}
|
||||
|
||||
unsigned kiwi_solver_get_error_mask(const KiwiSolver* s) {
|
||||
return lk_likely(s) ? s->error_mask : 0;
|
||||
}
|
||||
|
||||
void kiwi_solver_set_error_mask(KiwiSolver* s, unsigned mask) {
|
||||
if (lk_likely(s))
|
||||
s->error_mask = mask;
|
||||
}
|
||||
|
||||
const KiwiErr* kiwi_solver_add_constraint(KiwiSolver* s, const KiwiConstraint* constraint) {
|
||||
return wrap_err(s, constraint, [](auto& s, const auto& c) { s.addConstraint(c); });
|
||||
}
|
||||
|
||||
const KiwiErr* kiwi_solver_remove_constraint(KiwiSolver* s, const KiwiConstraint* constraint) {
|
||||
return wrap_err(s, constraint, [](auto& s, const auto& c) { s.removeConstraint(c); });
|
||||
}
|
||||
|
||||
bool kiwi_solver_has_constraint(const KiwiSolver* s, const KiwiConstraint* constraint) {
|
||||
if (lk_unlikely(!s || !constraint))
|
||||
return 0;
|
||||
return s->solver.hasConstraint(*constraint);
|
||||
}
|
||||
|
||||
const KiwiErr* kiwi_solver_add_edit_var(KiwiSolver* s, const KiwiVar* var, double strength) {
|
||||
return wrap_err(s, var, [strength](auto& s, const auto& v) {
|
||||
s.addEditVariable(v, strength);
|
||||
});
|
||||
}
|
||||
|
||||
const KiwiErr* kiwi_solver_remove_edit_var(KiwiSolver* s, const KiwiVar* var) {
|
||||
return wrap_err(s, var, [](auto& s, const auto& v) { s.removeEditVariable(v); });
|
||||
}
|
||||
|
||||
bool kiwi_solver_has_edit_var(const KiwiSolver* s, const KiwiVar* var) {
|
||||
if (lk_unlikely(!s || !var))
|
||||
return 0;
|
||||
return s->solver.hasEditVariable(*var);
|
||||
}
|
||||
|
||||
const KiwiErr* kiwi_solver_suggest_value(KiwiSolver* s, const KiwiVar* var, double value) {
|
||||
return wrap_err(s, var, [value](auto& s, const auto& v) { s.suggestValue(v, value); });
|
||||
}
|
||||
|
||||
void kiwi_solver_update_vars(KiwiSolver* s) {
|
||||
if (lk_likely(s))
|
||||
s->solver.updateVariables();
|
||||
}
|
||||
|
||||
void kiwi_solver_reset(KiwiSolver* s) {
|
||||
if (lk_likely(s))
|
||||
s->solver.reset();
|
||||
}
|
||||
|
||||
void kiwi_solver_dump(const KiwiSolver* s) {
|
||||
if (lk_likely(s))
|
||||
s->solver.dump();
|
||||
}
|
||||
|
||||
char* kiwi_solver_dumps(const KiwiSolver* s) {
|
||||
if (lk_unlikely(!s))
|
||||
return nullptr;
|
||||
|
||||
const auto& str = s->solver.dumps();
|
||||
const auto buf_size = str.size() + 1;
|
||||
auto* buf = static_cast<char*>(std::malloc(buf_size));
|
||||
if (!buf)
|
||||
return nullptr;
|
||||
std::memcpy(buf, str.c_str(), str.size() + 1);
|
||||
return buf;
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
139
ckiwi/ckiwi.h
Normal file
139
ckiwi/ckiwi.h
Normal file
@@ -0,0 +1,139 @@
|
||||
#ifndef LJKIWI_CKIWI_H_
|
||||
#define LJKIWI_CKIWI_H_
|
||||
|
||||
#if !defined(_MSC_VER) || _MSC_VER >= 1900
|
||||
#undef LJKIWI_USE_FAM_1
|
||||
#else
|
||||
#define LJKIWI_USE_FAM_1
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
namespace kiwi {
|
||||
class Variable;
|
||||
class Constraint;
|
||||
} // namespace kiwi
|
||||
|
||||
extern "C" {
|
||||
|
||||
typedef kiwi::Variable KiwiVar;
|
||||
typedef kiwi::Constraint KiwiConstraint;
|
||||
#else
|
||||
typedef struct KiwiVar KiwiVar;
|
||||
typedef struct KiwiConstraint KiwiConstraint;
|
||||
#endif
|
||||
|
||||
typedef struct KiwiTypeInfo {
|
||||
unsigned size;
|
||||
unsigned align;
|
||||
} KiwiTypeInfo;
|
||||
|
||||
#if __GNUC__
|
||||
#pragma GCC visibility push(default)
|
||||
#endif
|
||||
|
||||
extern KiwiTypeInfo kiwi_ti_KiwiVar, kiwi_ti_KiwiConstraint, kiwi_ti_KiwiSolver;
|
||||
|
||||
// LuaJIT start
|
||||
enum KiwiErrKind {
|
||||
KiwiErrNone,
|
||||
KiwiErrUnsatisfiableConstraint = 1,
|
||||
KiwiErrUnknownConstraint,
|
||||
KiwiErrDuplicateConstraint,
|
||||
KiwiErrUnknownEditVariable,
|
||||
KiwiErrDuplicateEditVariable,
|
||||
KiwiErrBadRequiredStrength,
|
||||
KiwiErrInternalSolverError,
|
||||
KiwiErrAlloc,
|
||||
KiwiErrNullObject,
|
||||
KiwiErrUnknown,
|
||||
};
|
||||
|
||||
enum KiwiRelOp { KIWI_OP_LE, KIWI_OP_GE, KIWI_OP_EQ };
|
||||
|
||||
typedef struct KiwiTerm {
|
||||
KiwiVar* var;
|
||||
double coefficient;
|
||||
} KiwiTerm;
|
||||
|
||||
typedef struct KiwiExpression {
|
||||
double constant;
|
||||
int term_count;
|
||||
KiwiConstraint* owner;
|
||||
|
||||
#if defined(LJKIWI_LUAJIT_DEF)
|
||||
KiwiTerm terms_[?];
|
||||
#elif defined(LJKIWI_USE_FAM_1)
|
||||
KiwiTerm terms_[1]; // LuaJIT: struct KiwiTerm terms_[?];
|
||||
#else
|
||||
KiwiTerm terms_[];
|
||||
#endif
|
||||
|
||||
} KiwiExpression;
|
||||
|
||||
typedef struct KiwiErr {
|
||||
enum KiwiErrKind kind;
|
||||
const char* message;
|
||||
bool must_free;
|
||||
} KiwiErr;
|
||||
|
||||
struct KiwiSolver;
|
||||
|
||||
void kiwi_var_construct(const char* name, void* mem);
|
||||
void kiwi_var_release(KiwiVar* var);
|
||||
void kiwi_var_retain(KiwiVar* var);
|
||||
|
||||
const char* kiwi_var_name(const KiwiVar* var);
|
||||
void kiwi_var_set_name(KiwiVar* var, const char* name);
|
||||
double kiwi_var_value(const KiwiVar* var);
|
||||
void kiwi_var_set_value(KiwiVar* var, double value);
|
||||
bool kiwi_var_eq(const KiwiVar* var, const KiwiVar* other);
|
||||
|
||||
void kiwi_expression_retain(KiwiExpression* expr);
|
||||
void kiwi_expression_destroy(KiwiExpression* expr);
|
||||
|
||||
void kiwi_constraint_construct(
|
||||
const KiwiExpression* lhs,
|
||||
const KiwiExpression* rhs,
|
||||
enum KiwiRelOp op,
|
||||
double strength,
|
||||
void* mem
|
||||
);
|
||||
void kiwi_constraint_release(KiwiConstraint* c);
|
||||
void kiwi_constraint_retain(KiwiConstraint* c);
|
||||
|
||||
double kiwi_constraint_strength(const KiwiConstraint* c);
|
||||
enum KiwiRelOp kiwi_constraint_op(const KiwiConstraint* c);
|
||||
bool kiwi_constraint_violated(const KiwiConstraint* c);
|
||||
int kiwi_constraint_expression(KiwiConstraint* c, KiwiExpression* out, int out_size);
|
||||
|
||||
void kiwi_solver_construct(unsigned error_mask, void* mem);
|
||||
void kiwi_solver_destroy(KiwiSolver* s);
|
||||
unsigned kiwi_solver_get_error_mask(const KiwiSolver* s);
|
||||
void kiwi_solver_set_error_mask(KiwiSolver* s, unsigned mask);
|
||||
|
||||
const KiwiErr* kiwi_solver_add_constraint(KiwiSolver* s, const KiwiConstraint* constraint);
|
||||
const KiwiErr* kiwi_solver_remove_constraint(KiwiSolver* s, const KiwiConstraint* constraint);
|
||||
bool kiwi_solver_has_constraint(const KiwiSolver* s, const KiwiConstraint* constraint);
|
||||
const KiwiErr* kiwi_solver_add_edit_var(KiwiSolver* s, const KiwiVar* var, double strength);
|
||||
const KiwiErr* kiwi_solver_remove_edit_var(KiwiSolver* s, const KiwiVar* var);
|
||||
bool kiwi_solver_has_edit_var(const KiwiSolver* s, const KiwiVar* var);
|
||||
const KiwiErr* kiwi_solver_suggest_value(KiwiSolver* s, const KiwiVar* var, double value);
|
||||
void kiwi_solver_update_vars(KiwiSolver* sp);
|
||||
void kiwi_solver_reset(KiwiSolver* sp);
|
||||
void kiwi_solver_dump(const KiwiSolver* sp);
|
||||
char* kiwi_solver_dumps(const KiwiSolver* sp);
|
||||
// LuaJIT end
|
||||
|
||||
#if __GNUC__
|
||||
#pragma GCC visibility pop
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
// Local Variables:
|
||||
// mode: c++
|
||||
// End:
|
||||
#endif // LJKIWI_CKIWI_H_
|
||||
36
kiwi-scm-1.rockspec
Normal file
36
kiwi-scm-1.rockspec
Normal file
@@ -0,0 +1,36 @@
|
||||
rockspec_format = "3.0"
|
||||
package = "kiwi"
|
||||
version = "scm-1"
|
||||
source = {
|
||||
url = "git+https://github.com/jkl1337/ljkiwi",
|
||||
}
|
||||
description = {
|
||||
summary = "LuaJIT FFI and Lua binding for the Kiwi constraint solver.",
|
||||
detailed = [[
|
||||
kiwi is a LuaJIT FFI and Lua binding for the Kiwi constraint solver. Kiwi is a fast
|
||||
implementation of the Cassowary constraint solving algorithm. kiwi provides
|
||||
reasonably efficient bindings using the LuaJIT FFI and convential Lua C bindings.]],
|
||||
license = "MIT",
|
||||
issues_url = "https://github.com/jkl1337/ljkiwi/issues",
|
||||
maintainer = "John Luebs",
|
||||
}
|
||||
dependencies = {
|
||||
"lua >= 5.1",
|
||||
}
|
||||
|
||||
build = {
|
||||
type = "make",
|
||||
build_variables = {
|
||||
LUA = "$(LUA)",
|
||||
CFLAGS = "$(CFLAGS)",
|
||||
LUA_INCDIR = "$(LUA_INCDIR)",
|
||||
LIBFLAG = "$(LIBFLAG)",
|
||||
LIB_EXT = "$(LIB_EXTENSION)",
|
||||
OBJ_EXT = "$(OBJ_EXTENSION)",
|
||||
},
|
||||
install_variables = {
|
||||
INST_LIBDIR = "$(LIBDIR)",
|
||||
INST_LUADIR = "$(LUADIR)",
|
||||
LIB_EXT = "$(LIB_EXTENSION)",
|
||||
},
|
||||
}
|
||||
@@ -145,7 +145,7 @@ public:
|
||||
/* Dump a representation of the solver internals to stdout.
|
||||
|
||||
*/
|
||||
void dump()
|
||||
void dump() const
|
||||
{
|
||||
debug::dump( m_impl );
|
||||
}
|
||||
@@ -153,7 +153,7 @@ public:
|
||||
/* Dump a representation of the solver internals to a stream.
|
||||
|
||||
*/
|
||||
void dump( std::ostream& out )
|
||||
void dump( std::ostream& out ) const
|
||||
{
|
||||
debug::dump( m_impl, out );
|
||||
}
|
||||
@@ -161,7 +161,7 @@ public:
|
||||
/* Dump a representation of the solver internals to a string.
|
||||
|
||||
*/
|
||||
std::string dumps()
|
||||
std::string dumps() const
|
||||
{
|
||||
return debug::dumps( m_impl );
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ public:
|
||||
}
|
||||
|
||||
// operator== is used for symbolics
|
||||
bool equals(const Variable &other)
|
||||
bool equals(const Variable &other) const
|
||||
{
|
||||
return m_data == other.m_data;
|
||||
}
|
||||
|
||||
37
ljkiwi.def
Normal file
37
ljkiwi.def
Normal file
@@ -0,0 +1,37 @@
|
||||
EXPORTS
|
||||
luaopen_ljkiwi
|
||||
|
||||
kiwi_ti_KiwiVar
|
||||
kiwi_ti_KiwiConstraint
|
||||
kiwi_ti_KiwiSolver
|
||||
|
||||
kiwi_constraint_construct
|
||||
kiwi_constraint_expression
|
||||
kiwi_constraint_op
|
||||
kiwi_constraint_release
|
||||
kiwi_constraint_retain
|
||||
kiwi_constraint_strength
|
||||
kiwi_constraint_violated
|
||||
kiwi_expression_destroy
|
||||
kiwi_solver_add_constraint
|
||||
kiwi_solver_add_edit_var
|
||||
kiwi_solver_construct
|
||||
kiwi_solver_destroy
|
||||
kiwi_solver_dump
|
||||
kiwi_solver_dumps
|
||||
kiwi_solver_has_constraint
|
||||
kiwi_solver_has_edit_var
|
||||
kiwi_solver_remove_constraint
|
||||
kiwi_solver_remove_edit_var
|
||||
kiwi_solver_reset
|
||||
kiwi_solver_suggest_value
|
||||
kiwi_solver_update_vars
|
||||
kiwi_var_construct
|
||||
kiwi_var_destroy
|
||||
kiwi_var_eq
|
||||
kiwi_var_name
|
||||
kiwi_var_release
|
||||
kiwi_var_retain
|
||||
kiwi_var_set_name
|
||||
kiwi_var_set_value
|
||||
kiwi_var_value
|
||||
1
ljkiwi.hpp
Normal file
1
ljkiwi.hpp
Normal file
@@ -0,0 +1 @@
|
||||
#include <kiwi/kiwi.h>
|
||||
147
luakiwi/luacompat.h
Normal file
147
luakiwi/luacompat.h
Normal file
@@ -0,0 +1,147 @@
|
||||
#ifndef LKIWI_LUACOMPAT_H_
|
||||
#define LKIWI_LUACOMPAT_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif // __cplusplus
|
||||
#include <lauxlib.h>
|
||||
#include <lua.h>
|
||||
#include <lualib.h>
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif // __cplusplus
|
||||
|
||||
#if !defined(LUA_VERSION_NUM) || LUA_VERSION_NUM == 501
|
||||
|
||||
#define LUA_OPADD 0
|
||||
#define LUA_OPSUB 1
|
||||
#define LUA_OPMUL 2
|
||||
#define LUA_OPDIV 3
|
||||
#define LUA_OPMOD 4
|
||||
#define LUA_OPPOW 5
|
||||
#define LUA_OPUNM 6
|
||||
|
||||
static int lua_absindex(lua_State* L, int i) {
|
||||
if (i < 0 && i > LUA_REGISTRYINDEX)
|
||||
i += lua_gettop(L) + 1;
|
||||
return i;
|
||||
}
|
||||
|
||||
static lua_Number lua_tonumberx(lua_State* L, int i, int* isnum) {
|
||||
lua_Number n = lua_tonumber(L, i);
|
||||
if (isnum != NULL) {
|
||||
*isnum = (n != 0 || lua_isnumber(L, i));
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
static lua_Integer lua_tointegerx(lua_State* L, int i, int* isnum) {
|
||||
int ok = 0;
|
||||
lua_Number n = lua_tonumberx(L, i, &ok);
|
||||
if (ok) {
|
||||
if (n == (lua_Integer)n) {
|
||||
if (isnum)
|
||||
*isnum = 1;
|
||||
return (lua_Integer)n;
|
||||
}
|
||||
}
|
||||
if (isnum)
|
||||
*isnum = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const char* luaL_tolstring(lua_State* L, int idx, size_t* len) {
|
||||
if (!luaL_callmeta(L, idx, "__tostring")) {
|
||||
int t = lua_type(L, idx), tt = 0;
|
||||
char const* name = NULL;
|
||||
switch (t) {
|
||||
case LUA_TNIL:
|
||||
lua_pushliteral(L, "nil");
|
||||
break;
|
||||
case LUA_TSTRING:
|
||||
case LUA_TNUMBER:
|
||||
lua_pushvalue(L, idx);
|
||||
break;
|
||||
case LUA_TBOOLEAN:
|
||||
if (lua_toboolean(L, idx))
|
||||
lua_pushliteral(L, "true");
|
||||
else
|
||||
lua_pushliteral(L, "false");
|
||||
break;
|
||||
default:
|
||||
tt = luaL_getmetafield(L, idx, "__name");
|
||||
name = (tt == LUA_TSTRING) ? lua_tostring(L, -1) : lua_typename(L, t);
|
||||
lua_pushfstring(L, "%s: %p", name, lua_topointer(L, idx));
|
||||
if (tt != LUA_TNIL)
|
||||
lua_replace(L, -2);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (!lua_isstring(L, -1))
|
||||
luaL_error(L, "'__tostring' must return a string");
|
||||
}
|
||||
return lua_tolstring(L, -1, len);
|
||||
}
|
||||
|
||||
#endif /* LUA_VERSION_NUM == 501 */
|
||||
|
||||
#if defined(LUA_VERSION_NUM) && LUA_VERSION_NUM <= 502
|
||||
|
||||
static void compat_reverse(lua_State* L, int a, int b) {
|
||||
for (; a < b; ++a, --b) {
|
||||
lua_pushvalue(L, a);
|
||||
lua_pushvalue(L, b);
|
||||
lua_replace(L, a);
|
||||
lua_replace(L, b);
|
||||
}
|
||||
}
|
||||
|
||||
static void lua_rotate(lua_State* L, int idx, int n) {
|
||||
int n_elems = 0;
|
||||
idx = lua_absindex(L, idx);
|
||||
n_elems = lua_gettop(L) - idx + 1;
|
||||
if (n < 0)
|
||||
n += n_elems;
|
||||
if (n > 0 && n < n_elems) {
|
||||
luaL_checkstack(L, 2, "not enough stack slots available");
|
||||
n = n_elems - n;
|
||||
compat_reverse(L, idx, idx + n - 1);
|
||||
compat_reverse(L, idx + n, idx + n_elems - 1);
|
||||
compat_reverse(L, idx, idx + n_elems - 1);
|
||||
}
|
||||
}
|
||||
|
||||
static int lua_geti(lua_State* L, int index, lua_Integer i) {
|
||||
index = lua_absindex(L, index);
|
||||
lua_pushinteger(L, i);
|
||||
lua_gettable(L, index);
|
||||
return lua_type(L, -1);
|
||||
}
|
||||
|
||||
#endif /* LUA_VERSION_NUM <= 502 */
|
||||
|
||||
#if defined(LUA_VERSION_NUM) && LUA_VERSION_NUM <= 503
|
||||
static int luaL_typeerror(lua_State* L, int arg, const char* tname) {
|
||||
const char* msg;
|
||||
const char* typearg; /* name for the type of the actual argument */
|
||||
if (luaL_getmetafield(L, arg, "__name") == LUA_TSTRING)
|
||||
typearg = lua_tostring(L, -1); /* use the given type name */
|
||||
else if (lua_type(L, arg) == LUA_TLIGHTUSERDATA)
|
||||
typearg = "light userdata"; /* special name for messages */
|
||||
else
|
||||
typearg = luaL_typename(L, arg); /* standard name */
|
||||
msg = lua_pushfstring(L, "%s expected, got %s", tname, typearg);
|
||||
return luaL_argerror(L, arg, msg);
|
||||
}
|
||||
|
||||
#endif /* LUA_VERSION_NUM <= 503 */
|
||||
|
||||
#if !defined(luaL_newlibtable)
|
||||
#define luaL_newlibtable(L, l) lua_createtable(L, 0, sizeof(l) / sizeof((l)[0]) - 1)
|
||||
#endif
|
||||
|
||||
#if !defined(luaL_checkversion)
|
||||
#define luaL_checkversion(L) ((void)0)
|
||||
#endif
|
||||
|
||||
#endif // LKIWI_LUACOMPAT_H_
|
||||
281
luakiwi/luakiwi-int.h
Normal file
281
luakiwi/luakiwi-int.h
Normal file
@@ -0,0 +1,281 @@
|
||||
#ifndef LUAKIWI_INT_H_
|
||||
#define LUAKIWI_INT_H_
|
||||
|
||||
#include <kiwi/kiwi.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <new>
|
||||
|
||||
#include "luacompat.h"
|
||||
|
||||
#if defined(__GNUC__) && !defined(LJKIWI_NO_BUILTIN)
|
||||
#define lk_likely(x) (__builtin_expect(((x) != 0), 1))
|
||||
#define lk_unlikely(x) (__builtin_expect(((x) != 0), 0))
|
||||
#else
|
||||
#define lk_likely(x) (x)
|
||||
#define lk_unlikely(x) (x)
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace kiwi;
|
||||
|
||||
// Lua 5.1 compatibility for missing lua_arith.
|
||||
inline void compat_arith_unm(lua_State* L) {
|
||||
#if defined(LUA_VERSION_NUM) && LUA_VERSION_NUM == 501
|
||||
lua_Number n = lua_tonumber(L, -1);
|
||||
if (n != 0 || lua_isnumber(L, -1)) {
|
||||
lua_pop(L, 1);
|
||||
lua_pushnumber(L, -n);
|
||||
} else {
|
||||
if (!luaL_callmeta(L, -1, "__unm"))
|
||||
luaL_error(L, "attempt to perform arithmetic on a %s value", luaL_typename(L, -1));
|
||||
lua_replace(L, -2);
|
||||
}
|
||||
#else
|
||||
lua_arith(L, LUA_OPUNM);
|
||||
#endif
|
||||
}
|
||||
|
||||
// This version supports placeholders.
|
||||
inline void setfuncs(lua_State* L, const luaL_Reg* l, int nup) {
|
||||
luaL_checkstack(L, nup, "too many upvalues");
|
||||
for (; l->name != NULL; l++) { /* fill the table with given functions */
|
||||
if (l->func == NULL) /* place holder? */
|
||||
lua_pushboolean(L, 0);
|
||||
else {
|
||||
for (int i = 0; i < nup; i++) /* copy upvalues to the top */
|
||||
lua_pushvalue(L, -nup);
|
||||
lua_pushcclosure(L, l->func, nup); /* closure with those upvalues */
|
||||
}
|
||||
lua_setfield(L, -(nup + 2), l->name);
|
||||
}
|
||||
lua_pop(L, nup); /* remove upvalues */
|
||||
}
|
||||
|
||||
template<typename T, std::size_t N>
|
||||
constexpr int array_count(T (&)[N]) {
|
||||
return static_cast<int>(N);
|
||||
}
|
||||
|
||||
inline void newlib(lua_State* L, const luaL_Reg* l) {
|
||||
lua_newtable(L);
|
||||
setfuncs(L, l, 0);
|
||||
}
|
||||
|
||||
enum KiwiErrKind {
|
||||
KiwiErrNone,
|
||||
KiwiErrUnsatisfiableConstraint = 1,
|
||||
KiwiErrUnknownConstraint,
|
||||
KiwiErrDuplicateConstraint,
|
||||
KiwiErrUnknownEditVariable,
|
||||
KiwiErrDuplicateEditVariable,
|
||||
KiwiErrBadRequiredStrength,
|
||||
KiwiErrInternalSolverError,
|
||||
KiwiErrAlloc,
|
||||
KiwiErrNullObject,
|
||||
KiwiErrUnknown,
|
||||
};
|
||||
|
||||
struct KiwiTerm {
|
||||
Variable* var;
|
||||
double coefficient;
|
||||
};
|
||||
|
||||
struct KiwiExpression {
|
||||
double constant;
|
||||
int term_count;
|
||||
Constraint* owner;
|
||||
|
||||
#if !defined(_MSC_VER) || _MSC_VER >= 1900
|
||||
KiwiTerm terms[];
|
||||
|
||||
static constexpr std::size_t sz(int count) {
|
||||
return sizeof(KiwiExpression) + sizeof(KiwiTerm) * (count > 0 ? count : 0);
|
||||
}
|
||||
#else
|
||||
KiwiTerm terms[1];
|
||||
|
||||
static constexpr std::size_t sz(int count) {
|
||||
return sizeof(KiwiExpression) + sizeof(KiwiTerm) * (count > 1 ? count - 1 : 0);
|
||||
}
|
||||
#endif
|
||||
|
||||
KiwiExpression() = delete;
|
||||
KiwiExpression(const KiwiExpression&) = delete;
|
||||
KiwiExpression& operator=(const KiwiExpression&) = delete;
|
||||
~KiwiExpression() = delete;
|
||||
};
|
||||
|
||||
// This mechanism was initially designed for LuaJIT FFI.
|
||||
struct KiwiErr {
|
||||
enum KiwiErrKind kind;
|
||||
const char* message;
|
||||
bool must_delete;
|
||||
};
|
||||
|
||||
struct KiwiSolver {
|
||||
unsigned error_mask;
|
||||
Solver solver;
|
||||
};
|
||||
|
||||
inline const KiwiErr* new_error(const KiwiErr* base, const std::exception& ex) {
|
||||
if (!std::strcmp(ex.what(), base->message))
|
||||
return base;
|
||||
|
||||
const auto msg_n = std::strlen(ex.what()) + 1;
|
||||
|
||||
auto* mem = static_cast<char*>(::operator new(sizeof(KiwiErr) + msg_n, std::nothrow));
|
||||
if (!mem) {
|
||||
return base;
|
||||
}
|
||||
auto* msg = mem + sizeof(KiwiErr);
|
||||
std::memcpy(msg, ex.what(), msg_n);
|
||||
return new (mem) KiwiErr {base->kind, msg, true};
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
inline const KiwiErr* wrap_err(F&& f) {
|
||||
static const constexpr KiwiErr kKiwiErrUnhandledCxxException {
|
||||
KiwiErrUnknown,
|
||||
"An unhandled C++ exception occurred."};
|
||||
|
||||
try {
|
||||
f();
|
||||
} catch (const UnsatisfiableConstraint&) {
|
||||
static const constexpr KiwiErr err {
|
||||
KiwiErrUnsatisfiableConstraint,
|
||||
"The constraint cannot be satisfied."};
|
||||
return &err;
|
||||
} catch (const UnknownConstraint&) {
|
||||
static const constexpr KiwiErr err {
|
||||
KiwiErrUnknownConstraint,
|
||||
"The constraint has not been added to the solver."};
|
||||
return &err;
|
||||
|
||||
} catch (const DuplicateConstraint&) {
|
||||
static const constexpr KiwiErr err {
|
||||
KiwiErrDuplicateConstraint,
|
||||
"The constraint has already been added to the solver."};
|
||||
return &err;
|
||||
|
||||
} catch (const UnknownEditVariable&) {
|
||||
static const constexpr KiwiErr err {
|
||||
KiwiErrUnknownEditVariable,
|
||||
"The edit variable has not been added to the solver."};
|
||||
return &err;
|
||||
|
||||
} catch (const DuplicateEditVariable&) {
|
||||
static const constexpr KiwiErr err {
|
||||
KiwiErrDuplicateEditVariable,
|
||||
"The edit variable has already been added to the solver."};
|
||||
return &err;
|
||||
|
||||
} catch (const BadRequiredStrength&) {
|
||||
static const constexpr KiwiErr err {
|
||||
KiwiErrBadRequiredStrength,
|
||||
"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."};
|
||||
return new_error(&base, ex);
|
||||
} catch (std::bad_alloc&) {
|
||||
static const constexpr KiwiErr err {KiwiErrAlloc, "A memory allocation failed."};
|
||||
return &err;
|
||||
} catch (const std::exception& ex) {
|
||||
return new_error(&kKiwiErrUnhandledCxxException, ex);
|
||||
} catch (...) {
|
||||
return &kKiwiErrUnhandledCxxException;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template<typename P, typename R, typename F>
|
||||
inline const KiwiErr* wrap_err(P& s, F&& f) {
|
||||
return wrap_err([&]() { f(s); });
|
||||
}
|
||||
|
||||
template<typename P, typename R, typename F>
|
||||
inline const KiwiErr* wrap_err(P& s, R& ref, F&& f) {
|
||||
return wrap_err([&]() { f(s, ref); });
|
||||
}
|
||||
|
||||
inline Variable* kiwi_var_retain(Variable* var) {
|
||||
alignas(Variable) unsigned char buf[sizeof(Variable)];
|
||||
new (buf) Variable(*var);
|
||||
return var;
|
||||
}
|
||||
|
||||
inline Constraint* kiwi_constraint_retain(Constraint* c) {
|
||||
alignas(Constraint) unsigned char buf[sizeof(Constraint)];
|
||||
new (buf) Constraint(*c);
|
||||
return c;
|
||||
}
|
||||
|
||||
inline Constraint* kiwi_constraint_new(
|
||||
const KiwiExpression* lhs,
|
||||
const KiwiExpression* rhs,
|
||||
RelationalOperator op,
|
||||
double strength,
|
||||
void* mem
|
||||
) {
|
||||
if (strength < 0.0) {
|
||||
strength = kiwi::strength::required;
|
||||
}
|
||||
|
||||
std::vector<Term> terms;
|
||||
terms.reserve((lhs ? lhs->term_count : 0) + (rhs ? rhs->term_count : 0));
|
||||
|
||||
if (lhs) {
|
||||
for (auto* t = lhs->terms; t != lhs->terms + lhs->term_count; ++t) {
|
||||
terms.emplace_back(*t->var, t->coefficient);
|
||||
}
|
||||
}
|
||||
if (rhs) {
|
||||
for (auto* t = rhs->terms; t != rhs->terms + rhs->term_count; ++t) {
|
||||
terms.emplace_back(*t->var, -t->coefficient);
|
||||
}
|
||||
}
|
||||
return new (mem) Constraint(
|
||||
Expression(std::move(terms), (lhs ? lhs->constant : 0.0) - (rhs ? rhs->constant : 0.0)),
|
||||
static_cast<RelationalOperator>(op),
|
||||
strength
|
||||
);
|
||||
}
|
||||
|
||||
inline const KiwiErr* kiwi_solver_add_constraint(Solver& s, const Constraint& constraint) {
|
||||
return wrap_err(s, constraint, [](auto& solver, const auto& c) { solver.addConstraint(c); });
|
||||
}
|
||||
|
||||
inline const KiwiErr* kiwi_solver_remove_constraint(Solver& s, const Constraint& constraint) {
|
||||
return wrap_err(s, constraint, [](auto& solver, const auto& c) {
|
||||
solver.removeConstraint(c);
|
||||
});
|
||||
}
|
||||
|
||||
inline const KiwiErr* kiwi_solver_add_edit_var(Solver& s, const Variable& var, double strength) {
|
||||
return wrap_err(s, var, [strength](auto& solver, const auto& v) {
|
||||
solver.addEditVariable(v, strength);
|
||||
});
|
||||
}
|
||||
|
||||
inline const KiwiErr* kiwi_solver_remove_edit_var(Solver& s, const Variable& var) {
|
||||
return wrap_err(s, var, [](auto& solver, const auto& v) { solver.removeEditVariable(v); });
|
||||
}
|
||||
|
||||
inline const KiwiErr* kiwi_solver_suggest_value(Solver& s, const Variable& var, double value) {
|
||||
return wrap_err(s, var, [value](auto& solver, const auto& v) {
|
||||
solver.suggestValue(v, value);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Local Variables:
|
||||
// mode: c++
|
||||
// End:
|
||||
|
||||
#endif // LUAKIWI_INT_H_
|
||||
1689
luakiwi/luakiwi.cpp
Normal file
1689
luakiwi/luakiwi.cpp
Normal file
File diff suppressed because it is too large
Load Diff
125
spec/constraint_spec.lua
Normal file
125
spec/constraint_spec.lua
Normal file
@@ -0,0 +1,125 @@
|
||||
expose("module", function()
|
||||
require("kiwi")
|
||||
end)
|
||||
|
||||
describe("Constraint", function()
|
||||
local kiwi = require("kiwi")
|
||||
local LUA_VERSION = tonumber(_VERSION:match("%d+%.%d+"))
|
||||
|
||||
describe("construction", function()
|
||||
local v, lhs
|
||||
before_each(function()
|
||||
v = kiwi.Var("foo")
|
||||
lhs = v + 1
|
||||
end)
|
||||
|
||||
it("has correct type", function()
|
||||
assert.True(kiwi.is_constraint(kiwi.Constraint()))
|
||||
assert.False(kiwi.is_constraint(v))
|
||||
end)
|
||||
|
||||
it("default op and strength", function()
|
||||
local c = kiwi.Constraint(lhs)
|
||||
assert.equal("EQ", c:op())
|
||||
assert.equal(kiwi.strength.REQUIRED, c:strength())
|
||||
end)
|
||||
|
||||
it("configure op", function()
|
||||
local c = kiwi.Constraint(lhs, nil, "LE")
|
||||
assert.equal("LE", c:op())
|
||||
end)
|
||||
it("configure strength", function()
|
||||
local c = kiwi.Constraint(lhs, nil, "GE", kiwi.strength.STRONG)
|
||||
assert.equal(kiwi.strength.STRONG, c:strength())
|
||||
end)
|
||||
|
||||
-- TODO: standardize formatting
|
||||
it("formats well", function()
|
||||
local c = kiwi.Constraint(lhs)
|
||||
if LUA_VERSION <= 5.2 then
|
||||
assert.equal("1 foo + 1 == 0 | required", tostring(c))
|
||||
else
|
||||
assert.equal("1.0 foo + 1.0 == 0 | required", tostring(c))
|
||||
end
|
||||
|
||||
c = kiwi.Constraint(lhs * 2, nil, "GE", kiwi.strength.STRONG)
|
||||
if LUA_VERSION <= 5.2 then
|
||||
assert.equal("2 foo + 2 >= 0 | strong", tostring(c))
|
||||
else
|
||||
assert.equal("2.0 foo + 2.0 >= 0 | strong", tostring(c))
|
||||
end
|
||||
|
||||
c = kiwi.Constraint(lhs / 2, nil, "LE", kiwi.strength.MEDIUM)
|
||||
assert.equal("0.5 foo + 0.5 <= 0 | medium", tostring(c))
|
||||
|
||||
c = kiwi.Constraint(lhs, kiwi.Expression(3), "GE", kiwi.strength.WEAK)
|
||||
if LUA_VERSION <= 5.2 then
|
||||
assert.equal("1 foo + -2 >= 0 | weak", tostring(c))
|
||||
else
|
||||
assert.equal("1.0 foo + -2.0 >= 0 | weak", tostring(c))
|
||||
end
|
||||
end)
|
||||
|
||||
it("rejects invalid args", function()
|
||||
assert.error(function()
|
||||
local _ = kiwi.Constraint(1)
|
||||
end)
|
||||
assert.error(function()
|
||||
local _ = kiwi.Constraint(lhs, 1)
|
||||
end)
|
||||
assert.error(function()
|
||||
local _ = kiwi.Constraint("")
|
||||
end)
|
||||
assert.error(function()
|
||||
local _ = kiwi.Constraint(lhs, "")
|
||||
end)
|
||||
assert.error(function()
|
||||
local _ = kiwi.Constraint(lhs, nil, "foo")
|
||||
end)
|
||||
assert.error(function()
|
||||
local _ = kiwi.Constraint(lhs, nil, "LE", "foo")
|
||||
end)
|
||||
end)
|
||||
it("combines lhs and rhs", function()
|
||||
local v2 = kiwi.Var("bar")
|
||||
local rhs = kiwi.Expression(3, 5 * v2, 3 * v)
|
||||
local c = kiwi.Constraint(lhs, rhs)
|
||||
|
||||
local e = c:expression()
|
||||
local t = e:terms()
|
||||
assert.equal(2, #t)
|
||||
if t[1].var ~= v then
|
||||
t[1], t[2] = t[2], t[1]
|
||||
end
|
||||
assert.equal(v, t[1].var)
|
||||
assert.equal(-2.0, t[1].coefficient)
|
||||
assert.equal(v2, t[2].var)
|
||||
assert.equal(-5.0, t[2].coefficient)
|
||||
assert.equal(-2.0, e.constant)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("method", function()
|
||||
local c, v
|
||||
|
||||
before_each(function()
|
||||
v = kiwi.Var("foo")
|
||||
c = kiwi.Constraint(2 * v + 1)
|
||||
end)
|
||||
|
||||
it("violated", function()
|
||||
assert.True(c:violated())
|
||||
v:set(-0.5)
|
||||
assert.False(c:violated())
|
||||
end)
|
||||
|
||||
it("add/remove constraint", function()
|
||||
local s = kiwi.Solver()
|
||||
c:add_to(s)
|
||||
assert.True(s:has_constraint(c))
|
||||
|
||||
c:remove_from(s)
|
||||
assert.False(s:has_constraint(c))
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
335
spec/solver_spec.lua
Normal file
335
spec/solver_spec.lua
Normal file
@@ -0,0 +1,335 @@
|
||||
expose("module", function()
|
||||
require("kiwi")
|
||||
end)
|
||||
|
||||
describe("solver", function()
|
||||
local kiwi = require("kiwi")
|
||||
---@type kiwi.Solver
|
||||
local solver
|
||||
|
||||
before_each(function()
|
||||
solver = kiwi.Solver()
|
||||
end)
|
||||
|
||||
it("should create a solver", function()
|
||||
assert.True(kiwi.is_solver(solver))
|
||||
assert.False(kiwi.is_solver(kiwi.Term(kiwi.Var("v1"))))
|
||||
end)
|
||||
|
||||
describe("edit variables", function()
|
||||
local v1, v2, v3
|
||||
before_each(function()
|
||||
v1 = kiwi.Var("foo")
|
||||
v2 = kiwi.Var("bar")
|
||||
v3 = kiwi.Var("baz")
|
||||
end)
|
||||
|
||||
describe("add_edit_var", function()
|
||||
it("should add a variable", function()
|
||||
solver:add_edit_var(v1, kiwi.strength.STRONG)
|
||||
assert.True(solver:has_edit_var(v1))
|
||||
end)
|
||||
|
||||
it("should return the argument", function()
|
||||
assert.equal(v1, solver:add_edit_var(v1, kiwi.strength.STRONG))
|
||||
end)
|
||||
|
||||
it("should error on incorrect type", function()
|
||||
assert.error(function()
|
||||
solver:add_edit_var("", kiwi.strength.STRONG) ---@diagnostic disable-line: param-type-mismatch
|
||||
end)
|
||||
assert.error(function()
|
||||
solver:add_edit_var(v1, "") ---@diagnostic disable-line: param-type-mismatch
|
||||
end)
|
||||
end)
|
||||
|
||||
it("should require a strength argument", function()
|
||||
assert.error(function()
|
||||
solver:add_edit_var(v1) ---@diagnostic disable-line: missing-parameter
|
||||
end)
|
||||
end)
|
||||
|
||||
it("should error on duplicate variable", function()
|
||||
solver:add_edit_var(v1, kiwi.strength.STRONG)
|
||||
local _, err = pcall(function()
|
||||
return solver:add_edit_var(v1, kiwi.strength.STRONG)
|
||||
end)
|
||||
assert.True(kiwi.is_error(err))
|
||||
assert.True(kiwi.is_solver(err.solver))
|
||||
assert.equal(v1, err.item)
|
||||
assert.equal("KiwiErrDuplicateEditVariable", err.kind)
|
||||
assert.equal("The edit variable has already been added to the solver.", err.message)
|
||||
end)
|
||||
|
||||
it("should error on invalid strength", function()
|
||||
local _, err = pcall(function()
|
||||
return solver:add_edit_var(v1, kiwi.strength.REQUIRED)
|
||||
end)
|
||||
assert.True(kiwi.is_error(err))
|
||||
assert.True(kiwi.is_solver(err.solver))
|
||||
assert.equal(v1, err.item)
|
||||
assert.equal("KiwiErrBadRequiredStrength", err.kind)
|
||||
assert.equal("A required strength cannot be used in this context.", err.message)
|
||||
end)
|
||||
|
||||
it("should return errors for duplicate variables", function()
|
||||
solver:set_error_mask({ "KiwiErrDuplicateEditVariable", "KiwiErrBadRequiredStrength" })
|
||||
local ret, err = solver:add_edit_var(v1, kiwi.strength.STRONG)
|
||||
assert.Nil(err)
|
||||
|
||||
ret, err = solver:add_edit_var(v1, kiwi.strength.STRONG)
|
||||
|
||||
assert.equal(v1, ret)
|
||||
assert.True(kiwi.is_error(err))
|
||||
---@diagnostic disable: need-check-nil
|
||||
assert.True(kiwi.is_solver(err.solver))
|
||||
assert.equal(v1, err.item)
|
||||
assert.equal("KiwiErrDuplicateEditVariable", err.kind)
|
||||
assert.equal("The edit variable has already been added to the solver.", err.message)
|
||||
---@diagnostic enable: need-check-nil
|
||||
end)
|
||||
|
||||
it("should return errors for invalid strength", function()
|
||||
solver:set_error_mask({ "KiwiErrDuplicateEditVariable", "KiwiErrBadRequiredStrength" })
|
||||
|
||||
---@diagnostic disable: need-check-nil
|
||||
local ret, err = solver:add_edit_var(v2, kiwi.strength.REQUIRED)
|
||||
assert.equal(v2, ret)
|
||||
assert.True(kiwi.is_error(err))
|
||||
assert.True(kiwi.is_solver(err.solver))
|
||||
assert.equal(v2, err.item)
|
||||
assert.equal("KiwiErrBadRequiredStrength", err.kind)
|
||||
assert.equal("A required strength cannot be used in this context.", err.message)
|
||||
---@diagnostic enable: need-check-nil
|
||||
end)
|
||||
|
||||
it("tolerates a nil self", function()
|
||||
assert.error(function()
|
||||
kiwi.Solver.add_edit_var(nil, v1, kiwi.strength.STRONG) ---@diagnostic disable-line: param-type-mismatch
|
||||
end)
|
||||
end)
|
||||
|
||||
it("tolerates a nil var", function()
|
||||
assert.error(function()
|
||||
solver:add_edit_var(nil, kiwi.strength.STRONG) ---@diagnostic disable-line: param-type-mismatch
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("add_edit_vars", function()
|
||||
it("should add variables", function()
|
||||
solver:add_edit_vars({ v1, v2 }, kiwi.strength.STRONG)
|
||||
assert.True(solver:has_edit_var(v1))
|
||||
assert.True(solver:has_edit_var(v2))
|
||||
assert.False(solver:has_edit_var(v3))
|
||||
end)
|
||||
|
||||
it("should return the argument", function()
|
||||
local arg = { v1, v2, v3 }
|
||||
assert.equal(arg, solver:add_edit_vars(arg, kiwi.strength.STRONG))
|
||||
end)
|
||||
|
||||
it("should error on incorrect type", function()
|
||||
assert.error(function()
|
||||
solver:add_edit_vars(v1, kiwi.strength.STRONG) ---@diagnostic disable-line: param-type-mismatch
|
||||
end)
|
||||
assert.error(function()
|
||||
solver:add_edit_vars("", kiwi.strength.STRONG) ---@diagnostic disable-line: param-type-mismatch
|
||||
end)
|
||||
assert.error(function()
|
||||
solver:add_edit_vars(v1, "") ---@diagnostic disable-line: param-type-mismatch
|
||||
end)
|
||||
end)
|
||||
|
||||
it("should require a strength argument", function()
|
||||
assert.error(function()
|
||||
solver:add_edit_vars({ v1, v2 }) ---@diagnostic disable-line: missing-parameter
|
||||
end)
|
||||
end)
|
||||
|
||||
it("should error on duplicate variable", function()
|
||||
local _, err = pcall(function()
|
||||
return solver:add_edit_vars({ v1, v2, v3, v2, v3 }, kiwi.strength.STRONG)
|
||||
end)
|
||||
assert.True(kiwi.is_error(err))
|
||||
assert.True(kiwi.is_solver(err.solver))
|
||||
assert.equal(v2, err.item)
|
||||
assert.equal("KiwiErrDuplicateEditVariable", err.kind)
|
||||
assert.equal("The edit variable has already been added to the solver.", err.message)
|
||||
end)
|
||||
|
||||
it("should error on invalid strength", function()
|
||||
local _, err = pcall(function()
|
||||
return solver:add_edit_vars({ v1, v2 }, kiwi.strength.REQUIRED)
|
||||
end)
|
||||
assert.True(kiwi.is_error(err))
|
||||
assert.True(kiwi.is_solver(err.solver))
|
||||
assert.equal(v1, err.item)
|
||||
assert.equal("KiwiErrBadRequiredStrength", err.kind)
|
||||
assert.equal("A required strength cannot be used in this context.", err.message)
|
||||
end)
|
||||
|
||||
it("should return errors for duplicate variables", function()
|
||||
solver:set_error_mask({ "KiwiErrDuplicateEditVariable", "KiwiErrBadRequiredStrength" })
|
||||
local ret, err = solver:add_edit_vars({ v1, v2, v3 }, kiwi.strength.STRONG)
|
||||
assert.Nil(err)
|
||||
|
||||
local arg = { v1, v2, v3 }
|
||||
ret, err = solver:add_edit_vars(arg, kiwi.strength.STRONG)
|
||||
assert.equal(arg, ret)
|
||||
assert.True(kiwi.is_error(err))
|
||||
---@diagnostic disable: need-check-nil
|
||||
assert.True(kiwi.is_solver(err.solver))
|
||||
assert.equal(v1, err.item)
|
||||
assert.equal("KiwiErrDuplicateEditVariable", err.kind)
|
||||
assert.equal("The edit variable has already been added to the solver.", err.message)
|
||||
---@diagnostic enable: need-check-nil
|
||||
end)
|
||||
|
||||
it("should return errors for invalid strength", function()
|
||||
solver:set_error_mask({ "KiwiErrDuplicateEditVariable", "KiwiErrBadRequiredStrength" })
|
||||
arg = { v2, v3 }
|
||||
local ret, err = solver:add_edit_vars(arg, kiwi.strength.REQUIRED)
|
||||
assert.equal(arg, ret)
|
||||
assert.True(kiwi.is_error(err))
|
||||
---@diagnostic disable: need-check-nil
|
||||
assert.True(kiwi.is_solver(err.solver))
|
||||
assert.equal(v2, err.item)
|
||||
assert.equal("KiwiErrBadRequiredStrength", err.kind)
|
||||
assert.equal("A required strength cannot be used in this context.", err.message)
|
||||
---@diagnostic enable: need-check-nil
|
||||
end)
|
||||
|
||||
it("tolerates a nil self", function()
|
||||
assert.has_error(function()
|
||||
kiwi.Solver.add_edit_vars(nil, { v1, v2 }, kiwi.strength.STRONG) ---@diagnostic disable-line: param-type-mismatch
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("remove_edit_var", function()
|
||||
it("should remove a variable", function()
|
||||
solver:add_edit_vars({ v1, v2, v3 }, kiwi.strength.STRONG)
|
||||
assert.True(solver:has_edit_var(v2))
|
||||
solver:remove_edit_var(v2)
|
||||
assert.True(solver:has_edit_var(v1))
|
||||
assert.False(solver:has_edit_var(v2))
|
||||
assert.True(solver:has_edit_var(v3))
|
||||
end)
|
||||
|
||||
it("should return the argument", function()
|
||||
solver:add_edit_var(v1, kiwi.strength.STRONG)
|
||||
assert.equal(v1, solver:remove_edit_var(v1))
|
||||
end)
|
||||
|
||||
it("should error on incorrect type", function()
|
||||
assert.error(function()
|
||||
solver:remove_edit_var("") ---@diagnostic disable-line: param-type-mismatch
|
||||
end)
|
||||
assert.error(function()
|
||||
solver:remove_edit_var({ v1 }) ---@diagnostic disable-line: param-type-mismatch
|
||||
end)
|
||||
end)
|
||||
|
||||
it("should error on unknown variable", function()
|
||||
solver:add_edit_var(v1, kiwi.strength.STRONG)
|
||||
local _, err = pcall(function()
|
||||
return solver:remove_edit_var(v2)
|
||||
end)
|
||||
assert.True(kiwi.is_error(err))
|
||||
assert.True(kiwi.is_solver(err.solver))
|
||||
assert.equal(v2, err.item)
|
||||
assert.equal("KiwiErrUnknownEditVariable", err.kind)
|
||||
assert.equal("The edit variable has not been added to the solver.", err.message)
|
||||
end)
|
||||
|
||||
it("should return errors if requested", function()
|
||||
solver:set_error_mask({ "KiwiErrDuplicateEditVariable", "KiwiErrUnknownEditVariable" })
|
||||
|
||||
local ret, err = solver:remove_edit_var(v1)
|
||||
|
||||
assert.equal(v1, ret)
|
||||
assert.True(kiwi.is_error(err))
|
||||
---@diagnostic disable: need-check-nil
|
||||
assert.True(kiwi.is_solver(err.solver))
|
||||
assert.equal(v1, err.item)
|
||||
assert.equal("KiwiErrUnknownEditVariable", err.kind)
|
||||
assert.equal("The edit variable has not been added to the solver.", err.message)
|
||||
---@diagnostic enable: need-check-nil
|
||||
end)
|
||||
|
||||
it("tolerates a nil self", function()
|
||||
assert.has_error(function()
|
||||
kiwi.Solver.remove_edit_var(nil, v1) ---@diagnostic disable-line: param-type-mismatch
|
||||
end)
|
||||
end)
|
||||
|
||||
it("tolerates a nil var", function()
|
||||
assert.has_error(function()
|
||||
solver:remove_edit_var(nil) ---@diagnostic disable-line: param-type-mismatch
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("remove_edit_vars", function()
|
||||
it("should remove variables", function()
|
||||
solver:add_edit_vars({ v1, v2, v3 }, kiwi.strength.STRONG)
|
||||
assert.True(solver:has_edit_var(v2))
|
||||
assert.True(solver:has_edit_var(v3))
|
||||
|
||||
solver:remove_edit_vars({ v2, v3 })
|
||||
assert.False(solver:has_edit_var(v2))
|
||||
assert.False(solver:has_edit_var(v3))
|
||||
end)
|
||||
|
||||
it("should return the argument", function()
|
||||
local arg = { v1, v2, v3 }
|
||||
solver:add_edit_vars(arg, kiwi.strength.STRONG)
|
||||
assert.equal(arg, solver:remove_edit_vars(arg))
|
||||
end)
|
||||
|
||||
it("should error on incorrect type", function()
|
||||
assert.error(function()
|
||||
solver:remove_edit_vars(v1) ---@diagnostic disable-line: param-type-mismatch
|
||||
end)
|
||||
assert.error(function()
|
||||
solver:remove_edit_vars("") ---@diagnostic disable-line: param-type-mismatch
|
||||
end)
|
||||
end)
|
||||
|
||||
it("should error on unknown variables", function()
|
||||
local _, err = pcall(function()
|
||||
return solver:remove_edit_vars({ v2, v1 })
|
||||
end)
|
||||
assert.True(kiwi.is_error(err))
|
||||
assert.True(kiwi.is_solver(err.solver))
|
||||
assert.equal(v2, err.item)
|
||||
assert.equal("KiwiErrUnknownEditVariable", err.kind)
|
||||
assert.equal("The edit variable has not been added to the solver.", err.message)
|
||||
end)
|
||||
|
||||
it("should return errors for unknown variables", function()
|
||||
solver:set_error_mask({ "KiwiErrDuplicateEditVariable", "KiwiErrUnknownEditVariable" })
|
||||
local ret, err = solver:add_edit_vars({ v1, v2 }, kiwi.strength.STRONG)
|
||||
assert.Nil(err)
|
||||
|
||||
local arg = { v1, v2, v3 }
|
||||
ret, err = solver:remove_edit_vars(arg)
|
||||
assert.equal(arg, ret)
|
||||
assert.True(kiwi.is_error(err))
|
||||
---@diagnostic disable: need-check-nil
|
||||
assert.True(kiwi.is_solver(err.solver))
|
||||
assert.equal(v3, err.item)
|
||||
assert.equal("KiwiErrUnknownEditVariable", err.kind)
|
||||
assert.equal("The edit variable has not been added to the solver.", err.message)
|
||||
---@diagnostic enable: need-check-nil
|
||||
end)
|
||||
|
||||
it("tolerates a nil self", function()
|
||||
assert.has_error(function()
|
||||
kiwi.Solver.remove_edit_vars(nil, { v1, v2 }) ---@diagnostic disable-line: param-type-mismatch
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
186
spec/var_spec.lua
Normal file
186
spec/var_spec.lua
Normal file
@@ -0,0 +1,186 @@
|
||||
expose("module", function()
|
||||
require("kiwi")
|
||||
end)
|
||||
|
||||
describe("Var", function()
|
||||
local kiwi = require("kiwi")
|
||||
|
||||
it("construction", function()
|
||||
assert.True(kiwi.is_var(kiwi.Var()))
|
||||
assert.False(kiwi.is_var(kiwi.Constraint()))
|
||||
|
||||
assert.error(function()
|
||||
kiwi.Var({})
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("method", function()
|
||||
local v
|
||||
|
||||
before_each(function()
|
||||
v = kiwi.Var("goo")
|
||||
end)
|
||||
|
||||
it("has settable name", function()
|
||||
assert.equal("goo", v:name())
|
||||
v:set_name("Δ")
|
||||
assert.equal("Δ", v:name())
|
||||
assert.error(function()
|
||||
v:set_name({})
|
||||
end)
|
||||
end)
|
||||
|
||||
it("has a initial value of 0.0", function()
|
||||
assert.equal(0.0, v:value())
|
||||
end)
|
||||
|
||||
it("has a settable value", function()
|
||||
v:set(47.0)
|
||||
assert.equal(47.0, v:value())
|
||||
end)
|
||||
|
||||
it("neg", function()
|
||||
local neg = -v --[[@as kiwi.Term]]
|
||||
assert.True(kiwi.is_term(neg))
|
||||
assert.equal(v, neg.var)
|
||||
assert.equal(-1.0, neg.coefficient)
|
||||
end)
|
||||
|
||||
describe("bin op", function()
|
||||
local v2
|
||||
before_each(function()
|
||||
v2 = kiwi.Var("foo")
|
||||
end)
|
||||
|
||||
it("mul", function()
|
||||
for _, prod in ipairs({ v * 2.0, 2 * v }) do
|
||||
assert.True(kiwi.is_term(prod))
|
||||
assert.equal(v, prod.var)
|
||||
assert.equal(2.0, prod.coefficient)
|
||||
end
|
||||
|
||||
assert.error(function()
|
||||
local _ = v * v2
|
||||
end)
|
||||
end)
|
||||
|
||||
it("div", function()
|
||||
local quot = v / 2.0
|
||||
assert.True(kiwi.is_term(quot))
|
||||
assert.equal(v, quot.var)
|
||||
assert.equal(0.5, quot.coefficient)
|
||||
|
||||
assert.error(function()
|
||||
local _ = v / v2
|
||||
end)
|
||||
end)
|
||||
|
||||
it("add", function()
|
||||
for _, sum in ipairs({ v + 2.0, 2 + v }) do
|
||||
assert.True(kiwi.is_expression(sum))
|
||||
assert.equal(2.0, sum.constant)
|
||||
|
||||
local terms = sum:terms()
|
||||
assert.equal(1, #terms)
|
||||
assert.equal(1.0, terms[1].coefficient)
|
||||
assert.equal(v, terms[1].var)
|
||||
end
|
||||
|
||||
local sum = v + v2
|
||||
assert.True(kiwi.is_expression(sum))
|
||||
assert.equal(0, sum.constant)
|
||||
local terms = sum:terms()
|
||||
assert.equal(2, #terms)
|
||||
assert.equal(v, terms[1].var)
|
||||
assert.equal(1.0, terms[1].coefficient)
|
||||
assert.equal(v2, terms[2].var)
|
||||
assert.equal(1.0, terms[2].coefficient)
|
||||
|
||||
assert.error(function()
|
||||
local _ = v + "foo"
|
||||
end)
|
||||
assert.error(function()
|
||||
local _ = v + {}
|
||||
end)
|
||||
end)
|
||||
|
||||
it("sub", function()
|
||||
local constants = { -2, 2 }
|
||||
for i, diff in ipairs({ v - 2.0, 2 - v }) do
|
||||
local constant = constants[i]
|
||||
assert.True(kiwi.is_expression(diff))
|
||||
assert.equal(constant, diff.constant)
|
||||
|
||||
local terms = diff:terms()
|
||||
assert.equal(1, #terms)
|
||||
assert.equal(v, terms[1].var)
|
||||
assert.equal(constant < 0 and 1 or -1, terms[1].coefficient)
|
||||
end
|
||||
|
||||
local diff = v - v2
|
||||
assert.True(kiwi.is_expression(diff))
|
||||
assert.equal(0, diff.constant)
|
||||
local terms = diff:terms()
|
||||
assert.equal(2, #terms)
|
||||
assert.equal(v, terms[1].var)
|
||||
assert.equal(1.0, terms[1].coefficient)
|
||||
assert.equal(v2, terms[2].var)
|
||||
assert.equal(-1.0, terms[2].coefficient)
|
||||
|
||||
assert.error(function()
|
||||
local _ = v - "foo"
|
||||
end)
|
||||
assert.error(function()
|
||||
local _ = v - {}
|
||||
end)
|
||||
end)
|
||||
|
||||
it("constraint var op expr", function()
|
||||
local ops = { "LE", "EQ", "GE" }
|
||||
for i, meth in ipairs({ "le", "eq", "ge" }) do
|
||||
local c = v[meth](v, v2 + 1)
|
||||
assert.True(kiwi.is_constraint(c))
|
||||
|
||||
local e = c:expression()
|
||||
local t = e:terms()
|
||||
assert.equal(2, #t)
|
||||
|
||||
-- order can be randomized due to use of map
|
||||
if t[1].var ~= v then
|
||||
t[1], t[2] = t[2], t[1]
|
||||
end
|
||||
assert.equal(v, t[1].var)
|
||||
assert.equal(1.0, t[1].coefficient)
|
||||
assert.equal(v2, t[2].var)
|
||||
assert.equal(-1.0, t[2].coefficient)
|
||||
|
||||
assert.equal(-1, e.constant)
|
||||
assert.equal(ops[i], c:op())
|
||||
assert.equal(kiwi.strength.REQUIRED, c:strength())
|
||||
end
|
||||
end)
|
||||
|
||||
it("constraint var op var", function()
|
||||
for i, meth in ipairs({ "le", "eq", "ge" }) do
|
||||
local c = v[meth](v, v2)
|
||||
assert.True(kiwi.is_constraint(c))
|
||||
|
||||
local e = c:expression()
|
||||
local t = e:terms()
|
||||
assert.equal(2, #t)
|
||||
|
||||
-- order can be randomized due to use of map
|
||||
if t[1].var ~= v then
|
||||
t[1], t[2] = t[2], t[1]
|
||||
end
|
||||
assert.equal(v, t[1].var)
|
||||
assert.equal(1.0, t[1].coefficient)
|
||||
assert.equal(v2, t[2].var)
|
||||
assert.equal(-1.0, t[2].coefficient)
|
||||
|
||||
assert.equal(0, e.constant)
|
||||
end
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
32
t.lua
Normal file
32
t.lua
Normal file
@@ -0,0 +1,32 @@
|
||||
local kiwi = require("kiwi")
|
||||
|
||||
local c
|
||||
--debug.getupvalue
|
||||
do
|
||||
local v1 = kiwi.Var("v1")
|
||||
local v2 = kiwi.Var("v2")
|
||||
local v3 = kiwi.Var("v3")
|
||||
local v4 = kiwi.Var("v4")
|
||||
local v5 = kiwi.Var("v5")
|
||||
local c1 = (3 * v1 + 4 * v2 + 6 * v3):eq(0)
|
||||
local c2 = (6 * v4 + 4 * v5 + 2 * v1 + 1.4 * v1 / 0.3):le(1000)
|
||||
|
||||
local e = c1:expression()
|
||||
local s = kiwi.Solver()
|
||||
|
||||
s:add_constraints({ c1, c2, c1 })
|
||||
print(s:dumps())
|
||||
end
|
||||
|
||||
-- c = (3 * v1 + 4 * v2 + 6 * v3):eq(0)
|
||||
|
||||
-- local t = c:expression():terms()
|
||||
-- print(t[2].var)
|
||||
|
||||
-- for k, v in ipairs(t) do
|
||||
-- print(k, v.var, v.coefficient)
|
||||
-- end
|
||||
|
||||
-- for k, v in pairs(kiwi.ErrKind) do
|
||||
-- print(k, v)
|
||||
-- end
|
||||
204
tmp/enaml.lua
Normal file
204
tmp/enaml.lua
Normal file
@@ -0,0 +1,204 @@
|
||||
local kiwi = require("kiwi")
|
||||
|
||||
local Var = kiwi.Var
|
||||
|
||||
---@param solver kiwi.Solver
|
||||
local function build_solver(solver)
|
||||
-- create custom strength
|
||||
--
|
||||
local width = Var("width")
|
||||
local height = Var("height")
|
||||
|
||||
local mmedium = kiwi.strength.create(0.0, 1.0, 0.0, 1.25)
|
||||
local smedium = kiwi.strength.create(0.0, 100, 0.0)
|
||||
local left = Var("left")
|
||||
local top = Var("top")
|
||||
local contents_top = Var("contents_top")
|
||||
local contents_bottom = Var("contents_bottom")
|
||||
local contents_left = Var("contents_left")
|
||||
local contents_right = Var("contents_right")
|
||||
local midline = Var("midline")
|
||||
local ctleft = Var("ctleft")
|
||||
local ctheight = Var("ctheight")
|
||||
local cttop = Var("cttop")
|
||||
local ctwidth = Var("ctwidth")
|
||||
local lb1left = Var("lb1left")
|
||||
local lb1height = Var("lb1height")
|
||||
local lb1top = Var("lb1top")
|
||||
local lb1width = Var("lb1width")
|
||||
local lb2left = Var("lb2left")
|
||||
local lb2height = Var("lb2height")
|
||||
local lb2top = Var("lb2top")
|
||||
local lb2width = Var("lb2width")
|
||||
local lb3left = Var("lb3left")
|
||||
local lb3height = Var("lb3height")
|
||||
local lb3top = Var("lb3top")
|
||||
local lb3width = Var("lb3width")
|
||||
local fl1left = Var("fl1left")
|
||||
local fl1height = Var("fl1height")
|
||||
local fl1top = Var("fl1top")
|
||||
local fl1width = Var("fl1width")
|
||||
local fl2left = Var("fl2left")
|
||||
local fl2height = Var("fl2height")
|
||||
local fl2top = Var("fl2top")
|
||||
local fl2width = Var("fl2width")
|
||||
local fl3left = Var("fl3left")
|
||||
local fl3height = Var("fl3height")
|
||||
local fl3top = Var("fl3top")
|
||||
local fl3width = Var("fl3width")
|
||||
|
||||
solver:add_edit_var(width, kiwi.strength.STRONG)
|
||||
solver:add_edit_var(height, kiwi.strength.STRONG)
|
||||
|
||||
local strong, medium, weak = kiwi.strength.STRONG, kiwi.strength.MEDIUM, kiwi.strength.WEAK
|
||||
local constraints = {
|
||||
(left + -0):ge(0),
|
||||
(height + 0):eq(0, medium),
|
||||
(top + -0):ge(0),
|
||||
(width + -0):ge(0),
|
||||
(height + -0):ge(0),
|
||||
(-top + contents_top + -10):eq(0),
|
||||
(lb3height + -16):eq(0, strong),
|
||||
(lb3height + -16):ge(0, strong),
|
||||
(ctleft + -0):ge(0),
|
||||
(cttop + -0):ge(0),
|
||||
(ctwidth + -0):ge(0),
|
||||
(ctheight + -0):ge(0),
|
||||
(fl3left + -0):ge(0),
|
||||
(ctheight + -24):ge(0, smedium),
|
||||
(ctwidth + -1.67772e+07):le(0, smedium),
|
||||
(ctheight + -24):le(0, smedium),
|
||||
(fl3top + -0):ge(0),
|
||||
(fl3width + -0):ge(0),
|
||||
(fl3height + -0):ge(0),
|
||||
(lb1width + -67):eq(0, weak),
|
||||
(lb2width + -0):ge(0),
|
||||
(lb2height + -0):ge(0),
|
||||
(fl2height + -0):ge(0),
|
||||
(lb3left + -0):ge(0),
|
||||
(fl2width + -125):ge(0, strong),
|
||||
(fl2height + -21):eq(0, strong),
|
||||
(fl2height + -21):ge(0, strong),
|
||||
(lb3top + -0):ge(0),
|
||||
(lb3width + -0):ge(0),
|
||||
(fl1left + -0):ge(0),
|
||||
(fl1width + -0):ge(0),
|
||||
(lb1width + -67):ge(0, strong),
|
||||
(fl2left + -0):ge(0),
|
||||
(lb2width + -66):eq(0, weak),
|
||||
(lb2width + -66):ge(0, strong),
|
||||
(lb2height + -16):eq(0, strong),
|
||||
(fl1height + -0):ge(0),
|
||||
(fl1top + -0):ge(0),
|
||||
(lb2top + -0):ge(0),
|
||||
(-lb2top + lb3top + -lb2height + -10):eq(0, mmedium),
|
||||
(-lb3top + -lb3height + fl3top + -10):ge(0),
|
||||
(-lb3top + -lb3height + fl3top + -10):eq(0, mmedium),
|
||||
(contents_bottom + -fl3height + -fl3top + -0):eq(0, mmedium),
|
||||
(fl1top + -contents_top + 0):ge(0),
|
||||
(fl1top + -contents_top + 0):eq(0, mmedium),
|
||||
(contents_bottom + -fl3height + -fl3top + -0):ge(0),
|
||||
(-left + -width + contents_right + 10):eq(0),
|
||||
(-top + -height + contents_bottom + 10):eq(0),
|
||||
(-left + contents_left + -10):eq(0),
|
||||
(lb3left + -contents_left + 0):eq(0, mmedium),
|
||||
(fl1left + -midline + 0):eq(0, strong),
|
||||
(fl2left + -midline + 0):eq(0, strong),
|
||||
(ctleft + -midline + 0):eq(0, strong),
|
||||
(fl1top + 0.5 * fl1height + -lb1top + -0.5 * lb1height + 0):eq(0, strong),
|
||||
(lb1left + -contents_left + 0):ge(0),
|
||||
(lb1left + -contents_left + 0):eq(0, mmedium),
|
||||
(-lb1left + fl1left + -lb1width + -10):ge(0),
|
||||
(-lb1left + fl1left + -lb1width + -10):eq(0, mmedium),
|
||||
(-fl1left + contents_right + -fl1width + -0):ge(0),
|
||||
(width + 0):eq(0, medium),
|
||||
(-fl1top + fl2top + -fl1height + -10):ge(0),
|
||||
(-fl1top + fl2top + -fl1height + -10):eq(0, mmedium),
|
||||
(cttop + -fl2top + -fl2height + -10):ge(0),
|
||||
(-ctheight + -cttop + fl3top + -10):ge(0),
|
||||
(contents_bottom + -fl3height + -fl3top + -0):ge(0),
|
||||
(cttop + -fl2top + -fl2height + -10):eq(0, mmedium),
|
||||
(-fl1left + contents_right + -fl1width + -0):eq(0, mmedium),
|
||||
(-lb2top + -0.5 * lb2height + fl2top + 0.5 * fl2height + 0):eq(0, strong),
|
||||
(-contents_left + lb2left + 0):ge(0),
|
||||
(-contents_left + lb2left + 0):eq(0, mmedium),
|
||||
(fl2left + -lb2width + -lb2left + -10):ge(0),
|
||||
(-ctheight + -cttop + fl3top + -10):eq(0, mmedium),
|
||||
(contents_bottom + -fl3height + -fl3top + -0):eq(0, mmedium),
|
||||
(lb1top + -0):ge(0),
|
||||
(lb1width + -0):ge(0),
|
||||
(lb1height + -0):ge(0),
|
||||
(fl2left + -lb2width + -lb2left + -10):eq(0, mmedium),
|
||||
(-fl2left + -fl2width + contents_right + -0):eq(0, mmedium),
|
||||
(-fl2left + -fl2width + contents_right + -0):ge(0),
|
||||
(lb3left + -contents_left + 0):ge(0),
|
||||
(lb1left + -0):ge(0),
|
||||
(0.5 * ctheight + cttop + -lb3top + -0.5 * lb3height + 0):eq(0, strong),
|
||||
(ctleft + -lb3left + -lb3width + -10):ge(0),
|
||||
(-ctwidth + -ctleft + contents_right + -0):ge(0),
|
||||
(ctleft + -lb3left + -lb3width + -10):eq(0, mmedium),
|
||||
(fl3left + -contents_left + 0):ge(0),
|
||||
(fl3left + -contents_left + 0):eq(0, mmedium),
|
||||
(-ctwidth + -ctleft + contents_right + -0):eq(0, mmedium),
|
||||
(-fl3left + contents_right + -fl3width + -0):eq(0, mmedium),
|
||||
(-contents_top + lb1top + 0):ge(0),
|
||||
(-contents_top + lb1top + 0):eq(0, mmedium),
|
||||
(-fl3left + contents_right + -fl3width + -0):ge(0),
|
||||
(lb2top + -lb1top + -lb1height + -10):ge(0),
|
||||
(-lb2top + lb3top + -lb2height + -10):ge(0),
|
||||
(lb2top + -lb1top + -lb1height + -10):eq(0, mmedium),
|
||||
(fl1height + -21):eq(0, strong),
|
||||
(fl1height + -21):ge(0, strong),
|
||||
(lb2left + -0):ge(0),
|
||||
(lb2height + -16):ge(0, strong),
|
||||
(fl2top + -0):ge(0),
|
||||
(fl2width + -0):ge(0),
|
||||
(lb1height + -16):ge(0, strong),
|
||||
(lb1height + -16):eq(0, strong),
|
||||
(fl3width + -125):ge(0, strong),
|
||||
(fl3height + -21):eq(0, strong),
|
||||
(fl3height + -21):ge(0, strong),
|
||||
(lb3height + -0):ge(0),
|
||||
(ctwidth + -119):ge(0, smedium),
|
||||
(lb3width + -24):eq(0, weak),
|
||||
(lb3width + -24):ge(0, strong),
|
||||
(fl1width + -125):ge(0, strong),
|
||||
}
|
||||
|
||||
for _, c in ipairs(constraints) do
|
||||
print(c)
|
||||
solver:add_constraint(c)
|
||||
end
|
||||
|
||||
return width, height
|
||||
end
|
||||
|
||||
local function main()
|
||||
local sizes = {
|
||||
{ w = 400, h = 600 },
|
||||
{ w = 600, h = 400 },
|
||||
{ w = 800, h = 1200 },
|
||||
{ w = 1200, h = 800 },
|
||||
{ w = 400, h = 800 },
|
||||
{ w = 800, h = 400 },
|
||||
}
|
||||
|
||||
for i = 1, 1 do
|
||||
local solver = kiwi.Solver()
|
||||
|
||||
local width, height = build_solver(solver)
|
||||
|
||||
for _, size in ipairs(sizes) do
|
||||
solver:suggest_value(width, size.w)
|
||||
solver:suggest_value(height, size.h)
|
||||
solver:update_vars()
|
||||
print(width:value(), height:value())
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
main()
|
||||
collectgarbage("collect")
|
||||
collectgarbage("collect")
|
||||
collectgarbage("collect")
|
||||
collectgarbage("collect")
|
||||
90
tmp/ex.lua
Normal file
90
tmp/ex.lua
Normal file
@@ -0,0 +1,90 @@
|
||||
local kiwi = require("kiwi")
|
||||
local Var = kiwi.Var
|
||||
|
||||
local Button = setmetatable({}, {
|
||||
__call = function(_, identifier)
|
||||
return setmetatable({
|
||||
left = Var(identifier .. " left"),
|
||||
width = Var(identifier .. " width"),
|
||||
}, {
|
||||
__tostring = function(self)
|
||||
return "Button(" .. self.left:value() .. ", " .. self.width:value() .. ")"
|
||||
end,
|
||||
})
|
||||
end,
|
||||
})
|
||||
|
||||
local b1 = Button("b1")
|
||||
local b2 = Button("b2")
|
||||
|
||||
local left_edge = Var("left")
|
||||
local right_edge = Var("width")
|
||||
|
||||
local STRONG = kiwi.Strength.STRONG
|
||||
|
||||
local constraints
|
||||
|
||||
for i = 1, 200 do
|
||||
q = kiwi.Term(left_edge, 2.0)
|
||||
end
|
||||
|
||||
for i = 1, 50, 1 do
|
||||
-- stylua: ignore start
|
||||
constraints = {
|
||||
left_edge :eq(0.0),
|
||||
-- two buttons are the same width
|
||||
b1.width :eq(b2.width),
|
||||
-- button1 starts 50 for the left margin
|
||||
(1.00 * b1.left) :eq(left_edge + 50),
|
||||
-- button2 ends 50 from the right margin
|
||||
right_edge :eq(b2.left + b2.width + 50),
|
||||
-- button2 starts at least 100 from the end of button1. This is the "elastic" constraint
|
||||
b2.left :ge(b1.left + b1.width + 100),
|
||||
-- button1 has a minimum width of 87
|
||||
b1.width :ge(87),
|
||||
-- button1 has a preferred width of 87
|
||||
b1.width :eq(87, STRONG),
|
||||
-- button2 has minimum width of 113
|
||||
b2.width :ge(113),
|
||||
-- button2 has a preferred width of 113
|
||||
b2.width :eq(113, STRONG),
|
||||
}
|
||||
-- stylua: ignore end
|
||||
--
|
||||
k = kiwi.Strength.create(0.0, 1.0, 0.0, 1.25)
|
||||
end
|
||||
local solver = kiwi.Solver()
|
||||
|
||||
for _, c in ipairs(constraints) do
|
||||
print(c)
|
||||
solver:add_constraint(c)
|
||||
end
|
||||
|
||||
solver:update_vars()
|
||||
|
||||
print(b1) -- Button(50, 113)
|
||||
print(b2) -- Button(263, 113)
|
||||
print(left_edge:value()) -- 0
|
||||
print(right_edge:value()) -- 426
|
||||
|
||||
solver:add_edit_var(right_edge, STRONG)
|
||||
solver:suggest_value(right_edge, 500)
|
||||
solver:update_vars()
|
||||
-- solver:dump()
|
||||
-- print(b1) -- Button(50, 113)
|
||||
-- print(b2) -- Button(337, 113)
|
||||
-- print(right_edge:value()) -- 500
|
||||
|
||||
-- print(solver:dumps())
|
||||
|
||||
-- solver = kiwi.Solver()
|
||||
|
||||
-- local trailing = Var("trailing")
|
||||
-- local leading = Var("leading")
|
||||
|
||||
-- solver:add_constraint(kiwi.new_single_constraint(trailing, 86))
|
||||
-- solver:add_constraint(kiwi.new_pair_constraint(trailing, leading, 8.0))
|
||||
-- print(solver:dumps())
|
||||
-- solver:update_vars()
|
||||
-- print(trailing:value()) -- 86
|
||||
-- print(leading:value()) -- 76
|
||||
28
tmp/ex2.lua
Normal file
28
tmp/ex2.lua
Normal file
@@ -0,0 +1,28 @@
|
||||
local kiwi = require("kiwi")
|
||||
|
||||
local Var = kiwi.Var
|
||||
|
||||
local function doit(v, i)
|
||||
v:set(i)
|
||||
end
|
||||
|
||||
local v = Var("fuckit")
|
||||
|
||||
local s = kiwi.Solver()
|
||||
|
||||
local e
|
||||
for i = 1, 1000 do
|
||||
e = kiwi.Expression(5.0, kiwi.Term(v, 3.0), kiwi.Term(v, 2.0))
|
||||
end
|
||||
--local e = (v + 1)
|
||||
|
||||
s:add_constraint(e:eq(100))
|
||||
|
||||
print(v:value())
|
||||
|
||||
s:update_vars()
|
||||
|
||||
v:set_name("fuck")
|
||||
print(v:value())
|
||||
|
||||
s:dump()
|
||||
49
tmp/ex3.lua
Normal file
49
tmp/ex3.lua
Normal file
@@ -0,0 +1,49 @@
|
||||
local kiwi = require("kiwi")
|
||||
local ffi = require("ffi")
|
||||
|
||||
local t
|
||||
local e
|
||||
local strformat = string.format
|
||||
local Strength = kiwi.Strength
|
||||
|
||||
--local bad_str = "-1 v8 + -1 v4 + -1 v2 + -4 v9 + -2 v3 + -1 v6 + -1 v5 + -1 v1 + -2 v7 + -5"
|
||||
-- local b = ""
|
||||
|
||||
function s(self)
|
||||
local ops = {
|
||||
[0] = "<=",
|
||||
">=",
|
||||
"==",
|
||||
}
|
||||
local strengths = {
|
||||
[Strength.REQUIRED] = "required",
|
||||
[Strength.STRONG] = "strong",
|
||||
[Strength.MEDIUM] = "medium",
|
||||
[Strength.WEAK] = "weak",
|
||||
}
|
||||
local strength = self:strength()
|
||||
--local e = self:expression()
|
||||
local s = strformat("%s %s %s", "aaa", tostring(self:expression()), tonumber(self:op()))
|
||||
return s
|
||||
end
|
||||
|
||||
for i = 1, 2000 do
|
||||
local v1 = kiwi.Var("v1")
|
||||
local v2 = kiwi.Var("v2")
|
||||
local v3 = kiwi.Var("v3")
|
||||
local v4 = kiwi.Var("v4")
|
||||
|
||||
local v5 = kiwi.Var("v5")
|
||||
local v6 = kiwi.Var("v6")
|
||||
local v7 = kiwi.Var("v7")
|
||||
local v8 = kiwi.Var("v8")
|
||||
local v9 = kiwi.Var("v9")
|
||||
local v10 = kiwi.Var("v9")
|
||||
|
||||
do
|
||||
--
|
||||
local c = ((-(v1 + v2 + 2 * v3 + v4 + 3)):eq(v5 + v6 + 2 * v7 + v8 + 4 * v9 + 3))
|
||||
s(c)
|
||||
end
|
||||
end
|
||||
print(t)
|
||||
37
tmp/ex4.lua
Normal file
37
tmp/ex4.lua
Normal file
@@ -0,0 +1,37 @@
|
||||
local kiwi = require("kiwi")
|
||||
local ffi = require("ffi")
|
||||
|
||||
local Var = kiwi.Var
|
||||
|
||||
local v1 = Var("v1")
|
||||
local v2 = Var("v2")
|
||||
local v3 = Var("v3")
|
||||
local v4 = Var("v4")
|
||||
local v5 = Var("v5")
|
||||
local v6 = Var("v6")
|
||||
|
||||
local f1 = kiwi.f1
|
||||
local f2 = kiwi.f2
|
||||
|
||||
local single = kiwi.constraints.single
|
||||
|
||||
local function execute_times(f, times)
|
||||
local begin = os.clock()
|
||||
for _ = 1, times do
|
||||
f()
|
||||
end
|
||||
local finish = os.clock()
|
||||
return finish - begin
|
||||
end
|
||||
|
||||
local t = execute_times(function()
|
||||
return kiwi.constraints.pair_ratio(v1, 2.0, v2, 3.0)
|
||||
end, 6000000)
|
||||
|
||||
print(t)
|
||||
|
||||
-- local t = execute_times(function()
|
||||
-- return pair_ratio2(v1, 2.0, v2, 3.0)
|
||||
-- end, 2000000)
|
||||
|
||||
-- print(t)
|
||||
41
tmp/ex5.lua
Normal file
41
tmp/ex5.lua
Normal file
@@ -0,0 +1,41 @@
|
||||
local kiwi = require("kiwi")
|
||||
|
||||
local Var = kiwi.Var
|
||||
|
||||
x1 = Var("x1")
|
||||
x2 = Var("x2")
|
||||
xm = Var("xm")
|
||||
|
||||
local constraints = { x1:ge(0), x2:le(100), x2:ge(x1 + 10), xm:eq((x1 + x2) / 2) }
|
||||
|
||||
local solver = kiwi.Solver()
|
||||
|
||||
for _, c in ipairs(constraints) do
|
||||
solver:add_constraint(c)
|
||||
end
|
||||
|
||||
local c = kiwi.constraints.single(x1, 40, "EQ", kiwi.Strength.WEAK)
|
||||
solver:add_constraint(c)
|
||||
|
||||
constraints[#constraints + 1] = c
|
||||
|
||||
solver:add_edit_var(xm, kiwi.Strength.STRONG)
|
||||
|
||||
solver:suggest_value(xm, 60)
|
||||
|
||||
solver:update_vars()
|
||||
|
||||
print(xm:value(), x1:value(), x2:value())
|
||||
|
||||
for _, c in ipairs(constraints) do
|
||||
print(c, c:violated())
|
||||
end
|
||||
|
||||
solver:suggest_value(xm, 90)
|
||||
solver:update_vars()
|
||||
|
||||
print(xm:value(), x1:value(), x2:value())
|
||||
|
||||
for _, c in ipairs(constraints) do
|
||||
print(c, c:violated())
|
||||
end
|
||||
26
tmp/t.lua
Normal file
26
tmp/t.lua
Normal file
@@ -0,0 +1,26 @@
|
||||
local k = require("kiwi")
|
||||
|
||||
s = k.Solver()
|
||||
|
||||
--s:set_error_mask({ "KiwiErrUnknownEditVariable" }, false)
|
||||
|
||||
local v1 = k.Var("v1")
|
||||
local v2 = k.Var("v2")
|
||||
local v3 = k.Var("v3")
|
||||
local v4 = k.Var("v4")
|
||||
|
||||
print((2 * v1 - 5 * v2))
|
||||
|
||||
os.exit()
|
||||
local c1 = k.constraints.pair_ratio(v1, -4, v2, 22, "GE", 1000)
|
||||
local c2 = k.constraints.pair_ratio(v3, -6, v4, 29, "LE", 1000)
|
||||
|
||||
local vo = c1:expression():terms()[1].var
|
||||
print(v1 == vo)
|
||||
-- print(v1:name())
|
||||
-- print(v1 * 5)
|
||||
|
||||
local e = k.Expression(6, k.Term(v1, 2), k.Term(v2, 3))
|
||||
--print(e)
|
||||
|
||||
--print(k.strength.MEDIUM)
|
||||
66
tmp/test.lua
Normal file
66
tmp/test.lua
Normal file
@@ -0,0 +1,66 @@
|
||||
local kiwi = require("kiwi")
|
||||
|
||||
local Var = kiwi.Var
|
||||
local Strength = kiwi.Strength
|
||||
|
||||
local Button_mt = {}
|
||||
|
||||
---@class Button
|
||||
---@field left kiwi.Var
|
||||
---@field width kiwi.Var
|
||||
---@overload fun(identifier: string): Button
|
||||
local Button = setmetatable({}, Button_mt)
|
||||
|
||||
function Button_mt.__call(_, identifier)
|
||||
return setmetatable({
|
||||
left = Var("left" .. identifier),
|
||||
width = Var("width" .. identifier),
|
||||
}, {
|
||||
__index = Button,
|
||||
__tostring = function(self)
|
||||
return "Button(" .. self.left:value() .. ", " .. self.width:value() .. ")"
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
local b1 = Button("b1")
|
||||
local b2 = Button("b2")
|
||||
|
||||
local left_limit = kiwi.Var("left")
|
||||
local right_limit = kiwi.Var("width")
|
||||
|
||||
|
||||
-- stylua: ignore start
|
||||
local constraints = {
|
||||
left_limit :eq( 0 ),
|
||||
b1.width :eq( b2.width ),
|
||||
b1.left :eq( left_limit + 50),
|
||||
right_limit:eq( b2.left + b2.width + 50),
|
||||
b2.left :ge( b1.left + b1.width + 100),
|
||||
|
||||
b1.width :ge( 87 ),
|
||||
b1.width :eq( 87, Strength.STRONG),
|
||||
|
||||
b2.width :ge( 113 ),
|
||||
b2.width :eq( 113, Strength.STRONG),
|
||||
}
|
||||
-- stylua: ignore end
|
||||
|
||||
local s = kiwi.Solver()
|
||||
|
||||
for _, c in ipairs(constraints) do
|
||||
s:add_constraint(c)
|
||||
end
|
||||
|
||||
print(constraints[4]:expression())
|
||||
print(constraints[5]:expression())
|
||||
|
||||
kiwi.Constraint(b1.left + 1, "EQ")
|
||||
|
||||
s:update_vars()
|
||||
|
||||
print(b1)
|
||||
print(b2)
|
||||
print(left_limit:value())
|
||||
print(right_limit:value())
|
||||
print(s:dumps())
|
||||
Reference in New Issue
Block a user