18 Commits

Author SHA1 Message Date
08e9bf08e7 Initial implementation of tuple demonstration
This allows allocating a buffer of double pointers pointing to
the memory location in a bunch of variables. It is not particularly
ergonomic as is, and it seems unlikely the real world performance
benefit will exist.
2024-02-27 21:59:10 -06:00
ef29b8abcb unprivate kiwi::VariableData 2024-02-27 21:53:16 -06:00
59cb4b3c4f Update README 2024-02-27 13:24:24 -06:00
55a3aa1e6f allow debugging from luarocks build 2024-02-27 12:59:58 -06:00
dc36e719eb squelch silly MSVC warning 2024-02-26 23:16:11 -06:00
d2e769ea30 Add release to workflow 2024-02-26 22:46:21 -06:00
3e56c503e4 fix code coverage 2024-02-26 22:24:48 -06:00
f68c24d9ea Add some more unit tests 2024-02-26 17:28:23 -06:00
2b76ba96ac Replace a few loops with ffi.copy where possible 2024-02-26 16:59:01 -06:00
98a3fff28f Add gcov 2024-02-26 14:53:49 -06:00
c35cea6213 Slightly more robust resource management 2024-02-26 12:47:46 -06:00
94a8bdca79 Better exception safety for Lua binding 2024-02-26 12:15:33 -06:00
ae5e4b3419 Makefile tweaks 2024-02-26 11:05:32 -06:00
3ffd84e348 release 0.1.0 2024-02-25 20:48:26 -06:00
6d7dbbfe74 CI attempt 1 2024-02-25 20:28:42 -06:00
f18610d526 add windows (MSVC) configuration
Update makefiles
2024-02-25 17:17:46 -06:00
9b245b10e3 Initial version, pending windows 2024-02-25 05:38:29 -06:00
359c31a0af Replace kiwi Constraint and Variable types
Going to replace these types since they are so stupid simple and
the originals are not conducive to integrating efficienctly outside
C++. We need a well defined way to get a pointer/reference to the
shared data. The proxy objects frustrate that, but they are
what is baked into the library. The public interface is not altered
except for the ability to access and construct from pointers.
2024-02-25 05:13:08 -06:00
32 changed files with 1481 additions and 1283 deletions

View File

@@ -65,7 +65,7 @@ PointerAlignment: Left
ReferenceAlignment: Left # New in v13. int &name ==> int& name
ReflowComments: false
SeparateDefinitionBlocks: Always # New in v14.
SortIncludes: false
SortIncludes: true
SortUsingDeclarations: true
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false

View File

@@ -1,3 +1,4 @@
---
name: Busted
on: [push, pull_request]
@@ -7,13 +8,25 @@ jobs:
strategy:
fail-fast: false
matrix:
lua_version: ["luajit-openresty", "luajit-2.1.0-beta3", "luajit-git"]
lua_version:
[
"luajit-openresty",
"luajit-2.1.0-beta3",
"luajit-git",
"5.4.6",
"5.1.5",
"5.3.6",
]
os: ["ubuntu-latest", "windows-latest", "macos-latest"]
runs-on: ubuntu-latest
runs-on: ${{ matrix.os }}
steps:
- name: Checkout
uses: actions/checkout@v4
- uses: ilammy/msvc-dev-cmd@v1
if: ${{ !startsWith(matrix.lua_version, 'luajit-') }}
- name: Setup lua
uses: jkl1337/gh-actions-lua@master
with:
@@ -23,14 +36,75 @@ jobs:
- 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
luarocks install luacov-reporter-lcov
- name: Build C++ library
run: |
luarocks make --no-install
env:
COVERALLS_REPO_TOKEN: ${{ github.token }}
LJKIWI_LUA: ${{ startsWith(matrix.lua_version, 'luajit-') && '0' || '1' }}
LJKIWI_CFFI: ${{ startsWith(matrix.lua_version, 'luajit-') && '1' || '0' }}
FCOV: ${{ startsWith(matrix.os, 'ubuntu-') && '1' || '' }}
# Can't assume so versions, have to update this manually below
FSANITIZE: ${{ matrix.os == 'ubuntu-latest' && '1' || '' }}
- name: Run busted tests
run: |
busted -c -v
env:
LD_PRELOAD: |-
${{ matrix.os == 'ubuntu-latest' &&
'/usr/lib/x86_64-linux-gnu/libasan.so.6:/usr/lib/x86_64-linux-gnu/libstdc++.so.6:/usr/lib/x86_64-linux-gnu/libubsan.so.1'
|| '' }}
- name: Run gcov
if: success() && startsWith(matrix.os, 'ubuntu-')
run: |
gcov -p -b -s"$(pwd)" -r *.gcda
rm -f 'kiwi#'*.gcov
- name: generate Lua lcov test reports
if: |-
success() && !startsWith(matrix.os, 'windows-')
&& startsWith(matrix.lua_version, 'luajit-')
run: luacov
- name: Report test coverage
if: |-
success() && !startsWith(matrix.os, 'windows-')
&& (startsWith(matrix.lua_version, 'luajit-') || startsWith(matrix.os, 'ubuntu-'))
continue-on-error: true
uses: coverallsapp/github-action@v2
with:
flag-name: run ${{ join(matrix.*, ' - ') }}
finish:
if: always()
needs: busted
runs-on: ubuntu-latest
steps:
- name: Close coveralls build
uses: coverallsapp/github-action@v2
with:
parallel-finished: true
publish:
if: github.ref_type == 'tag' && startsWith(github.ref_name, 'v')
needs: busted
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup lua
uses: jkl1337/gh-actions-lua@master
with:
luaVersion: "5.4.6"
- name: Setup luarocks
uses: jkl1337/gh-actions-luarocks@master
- name: Build C++ library
run: |
luarocks make --no-install
- name: Build rock
run: |
luarocks install dkjson
luarocks upload --api-key ${{ secrets.LUAROCKS_API_KEY }} \
rockspecs/kiwi-${GITHUB_REF_NAME#v}-1.rockspec

11
.gitignore vendored
View File

@@ -2,9 +2,20 @@
/lua
/lua_modules
/.luarocks
/config.mk
*.gcda
*.gcno
*.gcov
*.lcov
*.out
*.pch
*.gch
*.lib
*.so
*.o
*.obj
*.exp
*.dll
*.rock
.cache/
compile_commands.json

5
.luacov Normal file
View File

@@ -0,0 +1,5 @@
modules = {
kiwi = "kiwi.lua",
}
reporter = "lcov"
reportfile = "luacov.lcov"

164
Makefile
View File

@@ -1,59 +1,128 @@
CC := $(CROSS)gcc
CXX := $(CROSS)g++
CP := cp
RM := rm
LIBFLAG := -shared
LIB_EXT := so
LIB_EXT := $(if $(filter Windows_NT,$(OS)),dll,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 -fsanitize=alignment \
-fsanitize=shift -fsanitize=unreachable -fsanitize=bool -fsanitize=enum
SANITIZE_FLAGS := -fsanitize=undefined -fsanitize=address
ifdef FDEBUG
OPTFLAG := -O2
else
OPTFLAG := -Og -g
endif
COVERAGE_FLAGS := --coverage
LTO_FLAGS := -flto=auto
ifdef SANITIZE
CCFLAGS += $(SANITIZE_FLAGS)
endif
ifdef LTO
CCFLAGS += $(LTO_FLAGS)
endif
ifeq ($(OS),Windows_NT)
is_clang = $(filter %clang++,$(CXX))
is_gcc = $(filter %g++,$(CXX))
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
ifdef FSANITIZE
$(error "FSANITIZE is not supported on Windows")
endif
else
ifneq ($(filter %clang,$(CC)),)
CXX := $(patsubst %clang,%clang++,$(CC))
override CXXFLAGS += -pedantic -Wno-c99-extensions
PCH := ljkiwi.hpp.pch
endif
endif
uname_s := $(shell uname -s)
ifeq ($(uname_s),Darwin)
is_clang = 1
is_gcc =
ifdef LUA
LUA_VERSION ?= $(lastword $(shell $(LUA) -e "print(_VERSION)"))
endif
CC := env MACOSX_DEPLOYMENT_TARGET=11.0 gcc
CXX := env MACOSX_DEPLOYMENT_TARGET=11.0 g++
LIBFLAG := -bundle -undefined dynamic_lookup
ifndef LUA_VERSION
LJKIWI_CKIWI := 1
else
ifneq ($(LUA_VERSION),5.1)
LJKIWI_CKIWI :=
else
is_clang = $(filter %clang++,$(CXX))
is_gcc = $(filter %g++,$(CXX))
SANITIZE_FLAGS += -fsanitize=bounds-strict
endif
endif
OBJS := luakiwi.o
ifdef LJKIWI_CKIWI
OBJS += ckiwi.o
-include config.mk
ifeq ($(origin LUAROCKS), command line)
ifdef FCOV
CCFLAGS := $(patsubst -O%,,$(CFLAGS))
else
ifdef FDEBUG
CCFLAGS := $(patsubst -O%,,$(CFLAGS)) -Og -g
else
CCFLAGS := $(CFLAGS)
endif
endif
override CFLAGS := -std=c99 $(CCFLAGS)
ifneq ($(filter %gcc,$(CC)),)
CXX := $(patsubst %gcc,%g++,$(CC))
else
ifneq ($(filter %clang,$(CC)),)
CXX := $(patsubst %clang,%clang++,$(CC))
endif
endif
else
CCFLAGS += -fPIC $(OPTFLAG)
override CFLAGS += -std=c99 $(CCFLAGS)
endif
CCFLAGS += -Wall -fvisibility=hidden -Wformat=2 -Wconversion -Wimplicit-fallthrough
ifdef FCOV
CCFLAGS += $(COVERAGE_FLAGS)
endif
ifdef FSANITIZE
CCFLAGS += $(SANITIZE_FLAGS)
endif
ifdef FLTO
CCFLAGS += $(LTO_FLAGS)
endif
ifneq ($(is_gcc),)
#PCH := ljkiwi.hpp.gch
else
ifneq ($(is_clang),)
override CXXFLAGS += -pedantic -Wno-c99-extensions
#PCH := ljkiwi.hpp.pch
endif
endif
override CPPFLAGS += -I$(SRCDIR) -I$(SRCDIR)/kiwi -I"$(LUA_INCDIR)"
override CXXFLAGS += -std=c++17 -fno-rtti $(CCFLAGS)
ifeq ($(OS),Windows_NT)
override CPPFLAGS += -DLUA_BUILD_AS_DLL
override LIBFLAG += "$(LUA_LIBDIR)/$(LUALIB)"
endif
ifdef LUA
LUA_VERSION ?= $(lastword $(shell "$(LUA)" -e "print(_VERSION)"))
endif
ifdef LUA_VERSION
ifneq ($(LUA_VERSION),5.1)
LJKIWI_CFFI ?= 0
endif
endif
kiwi_lib_srcs := AssocVector.h constraint.h debug.h errors.h expression.h kiwi.h maptype.h \
row.h shareddata.h solver.h solverimpl.h strength.h symbol.h symbolics.h term.h \
util.h variable.h version.h
ifneq ($(LJKIWI_LUA),0)
objs += luakiwi.o
endif
ifneq ($(LJKIWI_CFFI),0)
objs += ckiwi.o
endif
vpath %.cpp $(SRCDIR)/ckiwi $(SRCDIR)/luakiwi
vpath %.h $(SRCDIR)/ckiwi $(SRCDIR)/luakiwi
vpath %.h $(SRCDIR)/ckiwi $(SRCDIR)/luakiwi $(SRCDIR)/kiwi/kiwi
all: ljkiwi.$(LIB_EXT)
@@ -61,25 +130,26 @@ 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)
mostlyclean:
$(RM) -f ljkiwi.$(LIB_EXT) $(objs) $(objs:.o=.gcda) $(objs:.o=.gcno)
ckiwi.o: $(PCH) ckiwi.cpp ckiwi.h
luakiwi.o: $(PCH) luakiwi-int.h luacompat.h
clean: mostlyclean
$(RM) -f $(PCH)
ljkiwi.$(LIB_EXT): $(OBJS)
$(CXX) $(CCFLAGS) $(LIBFLAG) -o $@ $(OBJS)
ckiwi.o: $(PCH) ckiwi.cpp ckiwi.h $(kiwi_lib_srcs)
luakiwi.o: $(PCH) luakiwi-int.h luacompat.h $(kiwi_lib_srcs)
$(PCH): $(kiwi_lib_srcs)
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 $@ $<
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -o $@ -x c++-header $<
%.o: %.cpp
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $<
.PHONY: all install clean
.PHONY: all install clean mostlyclean

21
Makefile.win Normal file
View File

@@ -0,0 +1,21 @@
T= ljkiwi
OBJS= luakiwi.obj
lib: $T.dll
{luakiwi\}.cpp.obj:
$(CC) $(CFLAGS) /W4 /wd4200 /c /D_CRT_SECURE_NO_DEPRECATE /I. /I kiwi /I"$(LUA_INCDIR)" /EHs /Fo$@ $(CFLAGS) $<
$T.dll: $(OBJS)
link $(LIBFLAG) /out:$T.dll "$(LUA_LIBDIR)\$(LUALIB)" $(OBJS)
IF EXIST $T.dll.manifest mt -manifest $T.dll.manifest -outputresource:$T.dll;2
install: $T.dll
IF NOT EXIST "$(INST_LIBDIR)" mkdir "$(INST_LIBDIR)"
copy $T.dll "$(INST_LIBDIR)"
copy kiwi.lua "$(INST_LUADIR)"
clean:
del $T.dll $(OBJS) $T.lib $T.exp
IF EXIST $T.dll.manifest del $T.dll.manifest

View File

@@ -1,21 +1,20 @@
ljkiwi - Free LuaJIT FFI kiwi (Cassowary derived) constraint solver.
ljkiwi - Free LuaJIT FFI and Lua C API kiwi (Cassowary derived) constraint solver.
[![CI](https://github.com/jkl1337/ljkiwi/actions/workflows/busted.yml/badge.svg)](https://github.com/jkl1337/ljkiwi/actions/workflows/busted.yml)
[![Coverage Status](https://coveralls.io/repos/github/jkl1337/ljkiwi/badge.svg?branch=master)](https://coveralls.io/github/jkl1337/ljkiwi?branch=master)
[![luarocks](https://img.shields.io/luarocks/v/jkl/ljkiwi)](https://luarocks.org/modules/jkl/ljkiwi)
[![luarocks](https://img.shields.io/luarocks/v/jkl/kiwi)](https://luarocks.org/modules/jkl/kiwi)
# 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.
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 and they appear mostly unmaintained.
Since the C++ Kiwi library is well tested, it was simpler to provide a LuaJIT FFI wrapper. Now, there is also a Lua C API binding with support for 5.1 through 5.4, and since
in most common use cases all the heavy lifting is done in the library, there is usually
no practical perfomance difference between the two.
This package has no dependencies other than a supported C++14 compiler 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
@@ -91,4 +90,4 @@ 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.
The API is fully annotated and will work with lua-language-server. Documentation can also be generated with lua-language-server.

View File

@@ -1,2 +0,0 @@
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

View File

@@ -1,10 +1,12 @@
#include "ljkiwi.hpp"
#include "ckiwi.h"
#include <kiwi/kiwi.h>
#include <climits>
#include <cstdarg>
#include <cstdlib>
#include <cstring>
#include <string>
#if defined(__GNUC__) && !defined(LJKIWI_NO_BUILTIN)
#define lk_likely(x) (__builtin_expect(((x) != 0), 1))
@@ -36,59 +38,69 @@ 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) {
const KiwiErr* wrap_err(F&& f) {
try {
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."};
@@ -102,7 +114,7 @@ inline const KiwiErr* wrap_err(F&& f) {
}
template<typename P, typename R, typename F>
inline const KiwiErr* wrap_err(P self, F&& f) {
const KiwiErr* wrap_err(P self, F&& f) {
if (lk_unlikely(!self)) {
return &kKiwiErrNullObjectArg0;
}
@@ -110,36 +122,51 @@ inline const KiwiErr* wrap_err(P self, F&& f) {
}
template<typename P, typename R, typename F>
inline const KiwiErr* wrap_err(P self, R item, F&& f) {
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); });
return wrap_err([&]() { f(self->solver, item); });
}
template<typename T, typename... Args>
T* make_unmanaged(Args... args) {
auto* o = new T(std::forward<Args>(args)...);
o->m_refcount = 1;
return o;
}
template<typename T>
void release_unmanaged(T* p) {
if (lk_likely(p)) {
if (--p->m_refcount == 0)
delete p;
}
}
template<typename T>
T* retain_unmanaged(T* p) {
if (lk_likely(p))
p->m_refcount++;
return p;
}
} // 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 : ""};
KiwiVar* kiwi_var_construct(const char* name) {
return make_unmanaged<VariableData>(lk_likely(name) ? name : "");
}
void kiwi_var_release(KiwiVar* var) {
if (lk_likely(var))
var->~KiwiVar();
release_unmanaged(var);
}
void kiwi_var_retain(KiwiVar* var) {
if (lk_likely(var)) {
alignas(KiwiVar) unsigned char buf[sizeof(KiwiVar)];
new (buf) KiwiVar(*var);
}
retain_unmanaged(var);
}
const char* kiwi_var_name(const KiwiVar* var) {
@@ -147,7 +174,7 @@ const char* kiwi_var_name(const KiwiVar* var) {
}
void kiwi_var_set_name(KiwiVar* var, const char* name) {
if (lk_likely(var))
if (lk_likely(var && name))
var->setName(name);
}
@@ -160,60 +187,60 @@ void kiwi_var_set_value(KiwiVar* var, double value) {
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);
retain_unmanaged(t->var);
}
expr->owner = expr;
}
void kiwi_expression_destroy(KiwiExpression* expr) {
if (lk_unlikely(!expr))
if (lk_unlikely(!expr || !expr->owner))
return;
if (expr->owner) {
expr->owner->~KiwiConstraint();
} else {
if (expr->owner == expr) {
for (auto* t = expr->terms_; t != expr->terms_ + expr->term_count; ++t) {
t->var->~KiwiVar();
release_unmanaged(t->var);
}
} else {
release_unmanaged(static_cast<ConstraintData*>(expr->owner));
}
}
void kiwi_constraint_construct(
KiwiConstraint* kiwi_constraint_construct(
const KiwiExpression* lhs,
const KiwiExpression* rhs,
enum KiwiRelOp op,
double strength,
void* mem
double strength
) {
if (strength < 0.0) {
strength = kiwi::strength::required;
}
std::vector<Term> terms;
terms.reserve((lhs ? lhs->term_count : 0) + (rhs ? rhs->term_count : 0));
terms.reserve(static_cast<decltype(terms)::size_type>(
(lhs && lhs->term_count > 0 ? lhs->term_count : 0)
+ (rhs && rhs->term_count > 0 ? 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);
for (int i = 0; i < lhs->term_count; ++i) {
const auto& t = lhs->terms_[i];
if (t.var)
terms.emplace_back(Variable(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);
for (int i = 0; i < rhs->term_count; ++i) {
const auto& t = rhs->terms_[i];
if (t.var)
terms.emplace_back(Variable(t.var), -t.coefficient);
}
}
new (mem) Constraint(
return make_unmanaged<ConstraintData>(
Expression(std::move(terms), (lhs ? lhs->constant : 0.0) - (rhs ? rhs->constant : 0.0)),
static_cast<RelationalOperator>(op),
strength
@@ -221,15 +248,11 @@ void kiwi_constraint_construct(
}
void kiwi_constraint_release(KiwiConstraint* c) {
if (lk_likely(c))
c->~KiwiConstraint();
release_unmanaged(c);
}
void kiwi_constraint_retain(KiwiConstraint* c) {
if (lk_likely(c)) {
alignas(KiwiConstraint) unsigned char buf[sizeof(KiwiConstraint)];
new (buf) KiwiConstraint(*c);
}
retain_unmanaged(c);
}
double kiwi_constraint_strength(const KiwiConstraint* c) {
@@ -250,20 +273,20 @@ int kiwi_constraint_expression(KiwiConstraint* c, KiwiExpression* out, int out_s
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;
int n = terms.size() < INT_MAX ? static_cast<int>(terms.size()) : INT_MAX;
if (!out || out_size < n)
return n;
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();
for (int i = 0; i < n; ++i) {
const auto& t = terms[static_cast<std::size_t>(i)];
out->terms_[i].var = const_cast<Variable&>(t.variable()).ptr();
out->terms_[i].coefficient = t.coefficient();
}
out->constant = expr.constant();
out->term_count = term_count;
out->owner = c;
kiwi_constraint_retain(c);
out->term_count = n;
out->owner = retain_unmanaged(c);
return term_count;
return n;
}
struct KiwiSolver {
@@ -271,15 +294,13 @@ struct KiwiSolver {
Solver solver;
};
KiwiTypeInfo kiwi_ti_KiwiSolver = {sizeof(KiwiSolver), alignof(KiwiSolver)};
void kiwi_solver_construct(unsigned error_mask, void* mem) {
new (mem) KiwiSolver {error_mask};
KiwiSolver* kiwi_solver_construct(unsigned error_mask) {
return new KiwiSolver {error_mask};
}
void kiwi_solver_destroy(KiwiSolver* s) {
if (lk_likely(s))
s->~KiwiSolver();
delete s;
}
unsigned kiwi_solver_get_error_mask(const KiwiSolver* s) {
@@ -291,38 +312,38 @@ void kiwi_solver_set_error_mask(KiwiSolver* s, unsigned mask) {
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_add_constraint(KiwiSolver* s, KiwiConstraint* constraint) {
return wrap_err(s, constraint, [](auto&& s, auto&& c) { s.addConstraint(Constraint(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); });
const KiwiErr* kiwi_solver_remove_constraint(KiwiSolver* s, KiwiConstraint* constraint) {
return wrap_err(s, constraint, [](auto&& s, auto&& c) { s.removeConstraint(Constraint(c)); });
}
bool kiwi_solver_has_constraint(const KiwiSolver* s, const KiwiConstraint* constraint) {
bool kiwi_solver_has_constraint(const KiwiSolver* s, KiwiConstraint* constraint) {
if (lk_unlikely(!s || !constraint))
return 0;
return s->solver.hasConstraint(*constraint);
return s->solver.hasConstraint(Constraint(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_add_edit_var(KiwiSolver* s, KiwiVar* var, double strength) {
return wrap_err(s, var, [strength](auto&& s, auto&& v) {
s.addEditVariable(Variable(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); });
const KiwiErr* kiwi_solver_remove_edit_var(KiwiSolver* s, KiwiVar* var) {
return wrap_err(s, var, [](auto&& s, auto&& v) { s.removeEditVariable(Variable(v)); });
}
bool kiwi_solver_has_edit_var(const KiwiSolver* s, const KiwiVar* var) {
bool kiwi_solver_has_edit_var(const KiwiSolver* s, KiwiVar* var) {
if (lk_unlikely(!s || !var))
return 0;
return s->solver.hasEditVariable(*var);
return s->solver.hasEditVariable(Variable(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); });
const KiwiErr* kiwi_solver_suggest_value(KiwiSolver* s, KiwiVar* var, double value) {
return wrap_err(s, var, [value](auto&& s, auto&& v) { s.suggestValue(Variable(v), value); });
}
void kiwi_solver_update_vars(KiwiSolver* s) {
@@ -353,4 +374,31 @@ char* kiwi_solver_dumps(const KiwiSolver* s) {
return buf;
}
void kiwi_tuple_init(KiwiTuple* tuple, int count, ...) {
if (lk_unlikely(!tuple))
return;
va_list args;
va_start(args, count);
for (int i = 0; i < count; ++i) {
auto* var = va_arg(args, KiwiVar*);
retain_unmanaged(var);
tuple->values[i] = &var->m_value;
}
tuple->count = count;
va_end(args);
}
void kiwi_tuple_destroy(KiwiTuple* tuple) {
if (lk_unlikely(!tuple))
return;
for (int i = 0; i < tuple->count; ++i) {
auto* value = const_cast<double*>(tuple->values[i]);
release_unmanaged(
reinterpret_cast<KiwiVar*>(reinterpret_cast<char*>(value) - offsetof(KiwiVar, m_value))
);
}
}
} // extern "C"

View File

@@ -10,30 +10,27 @@
#ifdef __cplusplus
namespace kiwi {
class Variable;
class Constraint;
class VariableData;
class ConstraintData;
} // namespace kiwi
typedef kiwi::VariableData KiwiVar;
typedef kiwi::ConstraintData KiwiConstraint;
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)
#if defined __GNUC__ && (!defined _WIN32 || defined __CYGWIN__)
#define LJKIWI_EXP __attribute__((visibility("default")))
#elif defined _WIN32
#define LJKIWI_EXP __declspec(dllexport)
#endif
extern KiwiTypeInfo kiwi_ti_KiwiVar, kiwi_ti_KiwiConstraint, kiwi_ti_KiwiSolver;
// LuaJIT start
enum KiwiErrKind {
KiwiErrNone,
@@ -59,12 +56,12 @@ typedef struct KiwiTerm {
typedef struct KiwiExpression {
double constant;
int term_count;
KiwiConstraint* owner;
void* owner;
#if defined(LJKIWI_LUAJIT_DEF)
KiwiTerm terms_[?];
#elif defined(LJKIWI_USE_FAM_1)
KiwiTerm terms_[1]; // LuaJIT: struct KiwiTerm terms_[?];
KiwiTerm terms_[1];
#else
KiwiTerm terms_[];
#endif
@@ -77,58 +74,67 @@ typedef struct KiwiErr {
bool must_free;
} KiwiErr;
typedef struct KiwiTuple {
int count;
#if defined(LJKIWI_LUAJIT_DEF)
const double* values[?];
#elif defined(LJKIWI_USE_FAM_1)
const double* values[1];
#else
const double* values[];
#endif
} KiwiTuple;
struct KiwiSolver;
void kiwi_var_construct(const char* name, void* mem);
void kiwi_var_release(KiwiVar* var);
void kiwi_var_retain(KiwiVar* var);
LJKIWI_EXP void kiwi_tuple_init(KiwiTuple* tuple, int count, ...);
LJKIWI_EXP void kiwi_tuple_destroy(KiwiTuple* tuple);
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);
LJKIWI_EXP KiwiVar* kiwi_var_construct(const char* name);
LJKIWI_EXP void kiwi_var_release(KiwiVar* var);
LJKIWI_EXP void kiwi_var_retain(KiwiVar* var);
void kiwi_expression_retain(KiwiExpression* expr);
void kiwi_expression_destroy(KiwiExpression* expr);
LJKIWI_EXP const char* kiwi_var_name(const KiwiVar* var);
LJKIWI_EXP void kiwi_var_set_name(KiwiVar* var, const char* name);
LJKIWI_EXP double kiwi_var_value(const KiwiVar* var);
LJKIWI_EXP void kiwi_var_set_value(KiwiVar* var, double value);
void kiwi_constraint_construct(
LJKIWI_EXP void kiwi_expression_retain(KiwiExpression* expr);
LJKIWI_EXP void kiwi_expression_destroy(KiwiExpression* expr);
LJKIWI_EXP KiwiConstraint* kiwi_constraint_construct(
const KiwiExpression* lhs,
const KiwiExpression* rhs,
enum KiwiRelOp op,
double strength,
void* mem
double strength
);
void kiwi_constraint_release(KiwiConstraint* c);
void kiwi_constraint_retain(KiwiConstraint* c);
LJKIWI_EXP void kiwi_constraint_release(KiwiConstraint* c);
LJKIWI_EXP 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);
LJKIWI_EXP double kiwi_constraint_strength(const KiwiConstraint* c);
LJKIWI_EXP enum KiwiRelOp kiwi_constraint_op(const KiwiConstraint* c);
LJKIWI_EXP bool kiwi_constraint_violated(const KiwiConstraint* c);
LJKIWI_EXP 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);
LJKIWI_EXP KiwiSolver* kiwi_solver_construct(unsigned error_mask);
LJKIWI_EXP void kiwi_solver_destroy(KiwiSolver* s);
LJKIWI_EXP unsigned kiwi_solver_get_error_mask(const KiwiSolver* s);
LJKIWI_EXP 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);
LJKIWI_EXP const KiwiErr* kiwi_solver_add_constraint(KiwiSolver* s, KiwiConstraint* constraint);
LJKIWI_EXP const KiwiErr*
kiwi_solver_remove_constraint(KiwiSolver* s, KiwiConstraint* constraint);
LJKIWI_EXP bool kiwi_solver_has_constraint(const KiwiSolver* s, KiwiConstraint* constraint);
LJKIWI_EXP const KiwiErr* kiwi_solver_add_edit_var(KiwiSolver* s, KiwiVar* var, double strength);
LJKIWI_EXP const KiwiErr* kiwi_solver_remove_edit_var(KiwiSolver* s, KiwiVar* var);
LJKIWI_EXP bool kiwi_solver_has_edit_var(const KiwiSolver* s, KiwiVar* var);
LJKIWI_EXP const KiwiErr* kiwi_solver_suggest_value(KiwiSolver* s, KiwiVar* var, double value);
LJKIWI_EXP void kiwi_solver_update_vars(KiwiSolver* sp);
LJKIWI_EXP void kiwi_solver_reset(KiwiSolver* sp);
LJKIWI_EXP void kiwi_solver_dump(const KiwiSolver* sp);
LJKIWI_EXP char* kiwi_solver_dumps(const KiwiSolver* sp);
// LuaJIT end
#if __GNUC__
#pragma GCC visibility pop
#endif
#ifdef __cplusplus
} // extern "C"
#endif

150
kiwi.lua
View File

@@ -24,29 +24,9 @@ kiwi.ljkiwi = ljkiwi
ffi.cdef([[
void free(void *);
typedef struct KiwiTypeInfo {
unsigned size;
unsigned align;
} KiwiTypeInfo;
extern KiwiTypeInfo kiwi_ti_KiwiVar, kiwi_ti_KiwiConstraint, kiwi_ti_KiwiSolver;
]])
for _, t in ipairs({ "KiwiVar", "KiwiConstraint", "KiwiSolver" }) do
local tinfo = ljkiwi[("kiwi_ti_%s"):format(t)] --[[@as any]]
ffi.cdef(
[[
typedef struct $ {
unsigned char b_[$];
} __attribute__((aligned($))) $;
]],
t,
tinfo.size,
tinfo.align,
t
)
end
typedef struct KiwiVar KiwiVar;
typedef struct KiwiConstraint KiwiConstraint;
typedef struct KiwiSolver KiwiSolver;]])
ffi.cdef([[
enum KiwiErrKind {
@@ -73,7 +53,8 @@ typedef struct KiwiTerm {
typedef struct KiwiExpression {
double constant;
int term_count;
KiwiConstraint* owner;
void* owner;
KiwiTerm terms_[?];
} KiwiExpression;
@@ -83,9 +64,17 @@ typedef struct KiwiErr {
bool must_free;
} KiwiErr;
typedef struct KiwiTuple {
int count;
const double* values[?];
} KiwiTuple;
struct KiwiSolver;
void kiwi_var_construct(const char* name, void* mem);
void kiwi_tuple_init(KiwiTuple* tuple, int count, ...);
void kiwi_tuple_destroy(KiwiTuple* tuple);
KiwiVar* kiwi_var_construct(const char* name);
void kiwi_var_release(KiwiVar* var);
void kiwi_var_retain(KiwiVar* var);
@@ -93,17 +82,15 @@ 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(
KiwiConstraint* kiwi_constraint_construct(
const KiwiExpression* lhs,
const KiwiExpression* rhs,
enum KiwiRelOp op,
double strength,
void* mem
double strength
);
void kiwi_constraint_release(KiwiConstraint* c);
void kiwi_constraint_retain(KiwiConstraint* c);
@@ -113,18 +100,18 @@ 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);
KiwiSolver* kiwi_solver_construct(unsigned error_mask);
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);
const KiwiErr* kiwi_solver_add_constraint(KiwiSolver* s, KiwiConstraint* constraint);
const KiwiErr* kiwi_solver_remove_constraint(KiwiSolver* s, KiwiConstraint* constraint);
bool kiwi_solver_has_constraint(const KiwiSolver* s, KiwiConstraint* constraint);
const KiwiErr* kiwi_solver_add_edit_var(KiwiSolver* s, KiwiVar* var, double strength);
const KiwiErr* kiwi_solver_remove_edit_var(KiwiSolver* s, KiwiVar* var);
bool kiwi_solver_has_edit_var(const KiwiSolver* s, KiwiVar* var);
const KiwiErr* kiwi_solver_suggest_value(KiwiSolver* s, KiwiVar* var, double value);
void kiwi_solver_update_vars(KiwiSolver* sp);
void kiwi_solver_reset(KiwiSolver* sp);
void kiwi_solver_dump(const KiwiSolver* sp);
@@ -196,38 +183,49 @@ function kiwi.is_var(o)
end
local Term = ffi.typeof("struct KiwiTerm") --[[@as kiwi.Term]]
local SIZEOF_TERM = assert(ffi.sizeof(Term))
kiwi.Term = Term
function kiwi.is_term(o)
return ffi_istype(Term, o)
end
local Expression = ffi.typeof("KiwiExpression") --[[@as kiwi.Expression]]
local Expression = ffi.typeof("struct KiwiExpression") --[[@as kiwi.Expression]]
kiwi.Expression = Expression
function kiwi.is_expression(o)
return ffi_istype(Expression, o)
end
local Constraint = ffi.typeof("KiwiConstraint") --[[@as kiwi.Constraint]]
local Constraint = ffi.typeof("struct KiwiConstraint") --[[@as kiwi.Constraint]]
kiwi.Constraint = Constraint
function kiwi.is_constraint(o)
return ffi_istype(Constraint, o)
end
local Tuple = ffi.typeof("struct KiwiTuple") --[[@as kiwi.Tuple]]
kiwi.Tuple = Tuple
---@class kiwi.Tuple: ffi.cdata*
---@field values ffi.cdata*
---@field count integer
---@overload fun(vars: kiwi.Var[]): kiwi.Tuple
ffi.metatype(Tuple, {
__new = function(self, vars)
local t = ffi_new(self, #vars)
ljkiwi.kiwi_tuple_init(t, #vars, unpack(vars))
return ffi_gc(t, ljkiwi.kiwi_tuple_destroy)
end,
})
---@param expr kiwi.Expression
---@param var kiwi.Var
---@param coeff number?
---@nodiscard
local function add_expr_term(expr, var, coeff)
local ret = ffi_gc(ffi_new(Expression, expr.term_count + 1), ljkiwi.kiwi_expression_destroy) --[[@as kiwi.Expression]]
for i = 0, expr.term_count - 1 do
local st = expr.terms_[i] --[[@as kiwi.Term]]
local dt = ret.terms_[i] --[[@as kiwi.Term]]
dt.var = st.var
dt.coefficient = st.coefficient
end
ffi_copy(ret.terms_, expr.terms_, SIZEOF_TERM * expr.term_count)
local dt = ret.terms_[expr.term_count]
dt.var = var
dt.coefficient = coeff or 1.0
@@ -248,7 +246,7 @@ local function new_expr_one(constant, var, coeff)
dt.coefficient = coeff or 1.0
ret.constant = constant
ret.term_count = 1
ljkiwi.kiwi_var_retain(var)
ljkiwi.kiwi_expression_retain(ret)
return ret
end
@@ -303,8 +301,6 @@ local OP_NAMES = {
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]]
@@ -343,9 +339,10 @@ local function rel(lhs, rhs, op, strength)
op_error(lhs, rhs, OP_NAMES[op])
end
local c = ffi_new(Constraint)
ljkiwi.kiwi_constraint_construct(el, er, op, strength or REQUIRED, c)
return ffi_gc(c, ljkiwi.kiwi_constraint_release) --[[@as kiwi.Constraint]]
return ffi_gc(
ljkiwi.kiwi_constraint_construct(el, er, op, strength or REQUIRED),
ljkiwi.kiwi_constraint_release
) --[[@as kiwi.Constraint]]
end
--- Define a constraint with expressions as `a <= b`.
@@ -431,9 +428,7 @@ do
}
function Var_mt:__new(name)
local v = ffi_new(self)
ljkiwi.kiwi_var_construct(name, v)
return ffi_gc(v, ljkiwi.kiwi_var_release)
return ffi_gc(ljkiwi.kiwi_var_construct(name), ljkiwi.kiwi_var_release)
end
function Var_mt.__mul(a, b)
@@ -477,10 +472,6 @@ do
return a + -b
end
function Var_mt:__eq(other)
return ljkiwi.kiwi_var_eq(self, other)
end
function Var_mt:__tostring()
return self:name() .. "(" .. self:value() .. ")"
end
@@ -526,8 +517,10 @@ do
end
function Term_mt.__new(T, var, coefficient)
local t = ffi_gc(ffi_new(T, var, coefficient or 1.0), term_gc)
local t = ffi_gc(ffi_new(T), term_gc) --[[@as kiwi.Term]]
ljkiwi.kiwi_var_retain(var)
t.var = var
t.coefficient = coefficient or 1.0
return t
end
@@ -646,13 +639,7 @@ do
---@nodiscard
local function new_expr_constant(expr, constant)
local ret = ffi_gc(ffi_new(Expression, expr.term_count), ljkiwi.kiwi_expression_destroy) --[[@as kiwi.Expression]]
for i = 0, expr.term_count - 1 do
local dt = ret.terms_[i] --[[@as kiwi.Term]]
local st = expr.terms_[i] --[[@as kiwi.Term]]
dt.var = st.var
dt.coefficient = st.coefficient
end
ffi_copy(ret.terms_, expr.terms_, SIZEOF_TERM * expr.term_count)
ret.constant = constant
ret.term_count = expr.term_count
ljkiwi.kiwi_expression_retain(ret)
@@ -820,9 +807,10 @@ do
}
function Constraint_mt:__new(lhs, rhs, op, strength)
local c = ffi_new(self)
ljkiwi.kiwi_constraint_construct(lhs, rhs, op or "EQ", strength or REQUIRED, c)
return ffi_gc(c, ljkiwi.kiwi_constraint_release)
return ffi_gc(
ljkiwi.kiwi_constraint_construct(lhs, rhs, op or "EQ", strength or REQUIRED),
ljkiwi.kiwi_constraint_release
)
end
local OPS = { [0] = "<=", ">=", "==" }
@@ -868,9 +856,10 @@ do
tmpexpr.constant = constant ~= nil and constant or 0
tmpexpr.term_count = 2
local c = ffi_new(Constraint)
ljkiwi.kiwi_constraint_construct(tmpexpr, nil, op or "EQ", strength or REQUIRED, c)
return ffi_gc(c, ljkiwi.kiwi_constraint_release) --[[@as kiwi.Constraint]]
return ffi_gc(
ljkiwi.kiwi_constraint_construct(tmpexpr, nil, op or "EQ", strength or REQUIRED),
ljkiwi.kiwi_constraint_release
) --[[@as kiwi.Constraint]]
end
local pair_ratio = constraints.pair_ratio
@@ -904,9 +893,10 @@ do
t.var = var
t.coefficient = 1.0
local c = ffi_new(Constraint)
ljkiwi.kiwi_constraint_construct(tmpexpr, nil, op or "EQ", strength or REQUIRED, c)
return ffi_gc(c, ljkiwi.kiwi_constraint_release) --[[@as kiwi.Constraint]]
return ffi_gc(
ljkiwi.kiwi_constraint_construct(tmpexpr, nil, op or "EQ", strength or REQUIRED),
ljkiwi.kiwi_constraint_release
) --[[@as kiwi.Constraint]]
end
end
@@ -1182,20 +1172,16 @@ do
__index = Solver_cls,
}
local Solver = ffi.typeof("struct KiwiSolver") --[[@as kiwi.Solver]]
kiwi.Solver = Solver
function Solver_mt:__new(error_mask)
if type(error_mask) == "table" then
error_mask = kiwi.error_mask(error_mask)
end
local s = ffi_new(Solver)
ljkiwi.kiwi_solver_construct(error_mask or 0, s)
return ffi_gc(s, ljkiwi.kiwi_solver_destroy) --[[@as kiwi.Constraint]]
return ffi_gc(ljkiwi.kiwi_solver_construct(error_mask or 0), ljkiwi.kiwi_solver_destroy) --[[@as kiwi.Constraint]]
end
kiwi.Solver = ffi.metatype(Solver, Solver_mt) --[[@as kiwi.Solver]]
local Solver = ffi.metatype(ffi.typeof("struct KiwiSolver"), Solver_mt) --[[@as kiwi.Solver]]
kiwi.Solver = Solver
function kiwi.is_solver(s)
return ffi_istype(Solver, s)

View File

@@ -26,17 +26,70 @@ enum RelationalOperator
OP_EQ
};
class Constraint
class Constraint;
class ConstraintData : public SharedData
{
static Expression reduce(const Expression &expr)
{
std::map<Variable, double> vars;
for (const auto & term : expr.terms())
vars[term.variable()] += term.coefficient();
std::vector<Term> terms(vars.begin(), vars.end());
return Expression(std::move(terms), expr.constant());
}
public:
ConstraintData(const Expression &expr,
RelationalOperator op,
double strength) : SharedData(),
m_expression(reduce(expr)),
m_strength(strength::clip(strength)),
m_op(op) {}
ConstraintData(const ConstraintData &other, double strength) : SharedData(),
m_expression(other.m_expression),
m_strength(strength::clip(strength)),
m_op(other.m_op) {}
~ConstraintData() = default;
const Expression &expression() const { return m_expression; }
RelationalOperator op() const { return m_op; }
double strength() const { return m_strength; }
bool violated() const
{
switch (m_op)
{
case OP_EQ: return !impl::nearZero(m_expression.value());
case OP_GE: return m_expression.value() < 0.0;
case OP_LE: return m_expression.value() > 0.0;
}
std::abort();
}
private:
Expression m_expression;
double m_strength;
RelationalOperator m_op;
ConstraintData(const ConstraintData &other) = delete;
ConstraintData &operator=(const ConstraintData &other) = delete;
};
class Constraint
{
public:
explicit Constraint(ConstraintData *p) : m_data(p) {}
Constraint() = default;
Constraint(const Expression &expr,
RelationalOperator op,
double strength = strength::required) : m_data(new ConstraintData(expr, op, strength)) {}
Constraint(const Constraint &other, double strength) : m_data(new ConstraintData(other, strength)) {}
Constraint(const Constraint &other, double strength) : m_data(new ConstraintData(*other.m_data, strength)) {}
Constraint(const Constraint &) = default;
@@ -44,32 +97,10 @@ public:
~Constraint() = default;
const Expression &expression() const
{
return m_data->m_expression;
}
RelationalOperator op() const
{
return m_data->m_op;
}
double strength() const
{
return m_data->m_strength;
}
bool violated() const
{
switch (m_data->m_op)
{
case OP_EQ: return !impl::nearZero(m_data->m_expression.value());
case OP_GE: return m_data->m_expression.value() < 0.0;
case OP_LE: return m_data->m_expression.value() > 0.0;
}
std::abort();
}
const Expression &expression() const { return m_data->expression(); }
RelationalOperator op() const { return m_data->op(); }
double strength() const { return m_data->strength(); }
bool violated() const { return m_data->violated(); }
bool operator!() const
{
@@ -81,46 +112,10 @@ public:
Constraint& operator=(Constraint &&) noexcept = default;
private:
static Expression reduce(const Expression &expr)
{
std::map<Variable, double> vars;
for (const auto & term : expr.terms())
vars[term.variable()] += term.coefficient();
std::vector<Term> terms(vars.begin(), vars.end());
return Expression(std::move(terms), expr.constant());
}
class ConstraintData : public SharedData
{
public:
ConstraintData(const Expression &expr,
RelationalOperator op,
double strength) : SharedData(),
m_expression(reduce(expr)),
m_strength(strength::clip(strength)),
m_op(op) {}
ConstraintData(const Constraint &other, double strength) : SharedData(),
m_expression(other.expression()),
m_strength(strength::clip(strength)),
m_op(other.op()) {}
~ConstraintData() = default;
Expression m_expression;
double m_strength;
RelationalOperator m_op;
private:
ConstraintData(const ConstraintData &other);
ConstraintData &operator=(const ConstraintData &other);
};
SharedDataPtr<ConstraintData> m_data;
public:
friend bool operator<(const Constraint &lhs, const Constraint &rhs)
{
return lhs.m_data < rhs.m_data;

View File

@@ -13,63 +13,54 @@
namespace kiwi
{
class VariableData : public SharedData
{
public:
VariableData(std::string name) : SharedData(),
m_name(std::move(name)),
m_value(0.0) {}
VariableData(const char *name) : SharedData(),
m_name(name),
m_value(0.0) {}
~VariableData() = default;
const std::string &name() const { return m_name; }
void setName(const char *name) { m_name = name; }
void setName(const std::string &name) { m_name = name; }
double value() const { return m_value; }
void setValue(double value) { m_value = value; }
std::string m_name;
double m_value;
VariableData(const VariableData &other) = delete;
VariableData &operator=(const VariableData &other) = delete;
};
class Variable
{
public:
class Context
{
public:
Context() = default;
virtual ~Context() {} // LCOV_EXCL_LINE
};
explicit Variable(VariableData *p) : m_data(p) {}
VariableData *ptr() { return m_data; }
Variable(Context *context = 0) : m_data(new VariableData("", context)) {}
Variable(std::string name, Context *context = 0) : m_data(new VariableData(std::move(name), context)) {}
Variable(const char *name, Context *context = 0) : m_data(new VariableData(name, context)) {}
Variable() : m_data(new VariableData("")) {}
Variable(std::string name) : m_data(new VariableData(std::move(name))) {}
Variable(const char *name) : m_data(new VariableData(name)) {}
Variable(const Variable&) = default;
Variable(Variable&&) noexcept = default;
~Variable() = default;
const std::string &name() const
{
return m_data->m_name;
}
const std::string &name() const { return m_data->name(); }
void setName(const char *name) { m_data->setName(name); }
void setName(const std::string &name) { m_data->setName(name); }
void setName(const char *name)
{
m_data->m_name = name;
}
void setName(const std::string &name)
{
m_data->m_name = name;
}
Context *context() const
{
return m_data->m_context.get();
}
void setContext(Context *context)
{
m_data->m_context.reset(context);
}
double value() const
{
return m_data->m_value;
}
void setValue(double value)
{
m_data->m_value = value;
}
double value() const { return m_data->value(); }
void setValue(double value) { m_data->setValue(value); }
// operator== is used for symbolics
bool equals(const Variable &other) const
@@ -82,32 +73,6 @@ public:
Variable& operator=(Variable&&) noexcept = default;
private:
class VariableData : public SharedData
{
public:
VariableData(std::string name, Context *context) : SharedData(),
m_name(std::move(name)),
m_context(context),
m_value(0.0) {}
VariableData(const char *name, Context *context) : SharedData(),
m_name(name),
m_context(context),
m_value(0.0) {}
~VariableData() = default;
std::string m_name;
std::unique_ptr<Context> m_context;
double m_value;
private:
VariableData(const VariableData &other);
VariableData &operator=(const VariableData &other);
};
SharedDataPtr<VariableData> m_data;
friend bool operator<(const Variable &lhs, const Variable &rhs)

View File

@@ -1,37 +0,0 @@
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

View File

@@ -1,33 +1,29 @@
#ifndef LKIWI_LUACOMPAT_H_
#define LKIWI_LUACOMPAT_H_
#ifndef LJKIWI_LUACOMPAT_H_
#define LJKIWI_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 && defined(__GNUC__)
#define LJKIWI_LJ_COMPAT_ATTR __attribute__((weak, visibility("default")))
#else
#define LJKIWI_LJ_COMPAT_ATTR static
#endif
#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) {
LJKIWI_LJ_COMPAT_ATTR 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));
@@ -35,7 +31,7 @@ static lua_Number lua_tonumberx(lua_State* L, int i, int* isnum) {
return n;
}
static lua_Integer lua_tointegerx(lua_State* L, int i, int* isnum) {
LJKIWI_LJ_COMPAT_ATTR lua_Integer lua_tointegerx(lua_State* L, int i, int* isnum) {
int ok = 0;
lua_Number n = lua_tonumberx(L, i, &ok);
if (ok) {
@@ -144,4 +140,8 @@ static int luaL_typeerror(lua_State* L, int arg, const char* tname) {
#define luaL_checkversion(L) ((void)0)
#endif
#endif // LKIWI_LUACOMPAT_H_
#ifdef __cplusplus
}
#endif // __cplusplus
#endif // LJKIWI_LUACOMPAT_H_

View File

@@ -23,8 +23,9 @@ 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)) {
int isnum;
lua_Number n = lua_tonumberx(L, -1, &isnum);
if (isnum) {
lua_pop(L, 1);
lua_pushnumber(L, -n);
} else {
@@ -78,26 +79,28 @@ enum KiwiErrKind {
};
struct KiwiTerm {
Variable* var;
VariableData* var;
double coefficient;
};
struct KiwiExpression {
double constant;
int term_count;
Constraint* owner;
ConstraintData* 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);
return sizeof(KiwiExpression)
+ sizeof(KiwiTerm) * static_cast<std::size_t>(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);
return sizeof(KiwiExpression)
+ sizeof(KiwiTerm) * static_cast<std::size_t>(count > 0 ? count : 0);
}
#endif
@@ -138,49 +141,57 @@ template<typename F>
inline const KiwiErr* wrap_err(F&& f) {
static const constexpr KiwiErr kKiwiErrUnhandledCxxException {
KiwiErrUnknown,
"An unhandled C++ exception occurred."};
"An unhandled C++ exception occurred."
};
try {
f();
} catch (const UnsatisfiableConstraint&) {
static const constexpr KiwiErr err {
KiwiErrUnsatisfiableConstraint,
"The constraint cannot be satisfied."};
"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."};
"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."};
"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."};
"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."};
"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."};
"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."};
@@ -194,81 +205,108 @@ inline const KiwiErr* wrap_err(F&& f) {
}
template<typename P, typename R, typename F>
inline const KiwiErr* wrap_err(P& s, F&& 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) {
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;
template<typename T, typename... Args>
inline T* make_unmanaged(Args... args) {
auto* p = new (std::nothrow) T(std::forward<Args>(args)...);
if (lk_unlikely(!p))
return nullptr;
p->m_refcount = 1;
return p;
}
inline Constraint* kiwi_constraint_retain(Constraint* c) {
alignas(Constraint) unsigned char buf[sizeof(Constraint)];
new (buf) Constraint(*c);
return c;
template<typename T>
inline void release_unmanaged(T* p) {
if (lk_likely(p)) {
if (--p->m_refcount == 0)
delete p;
}
}
inline Constraint* kiwi_constraint_new(
template<typename T>
inline T* retain_unmanaged(T* p) {
if (lk_likely(p))
p->m_refcount++;
return p;
}
inline ConstraintData* kiwi_constraint_new(
const KiwiExpression* lhs,
const KiwiExpression* rhs,
RelationalOperator op,
double strength,
void* mem
double strength
) {
if (strength < 0.0) {
strength = kiwi::strength::required;
}
try {
std::vector<Term> terms;
terms.reserve((lhs ? lhs->term_count : 0) + (rhs ? rhs->term_count : 0));
terms.reserve(static_cast<decltype(terms)::size_type>(
(lhs && lhs->term_count > 0 ? lhs->term_count : 0)
+ (rhs && rhs->term_count > 0 ? 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);
for (int i = 0; i < lhs->term_count; ++i) {
const auto& t = lhs->terms[i];
terms.emplace_back(Variable(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);
for (int i = 0; i < rhs->term_count; ++i) {
const auto& t = rhs->terms[i];
terms.emplace_back(Variable(t.var), -t.coefficient);
}
}
return new (mem) Constraint(
return make_unmanaged<ConstraintData>(
Expression(std::move(terms), (lhs ? lhs->constant : 0.0) - (rhs ? rhs->constant : 0.0)),
static_cast<RelationalOperator>(op),
strength
);
} catch (...) {
return nullptr;
}
}
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_constraint(Solver& s, ConstraintData* constraint) {
return wrap_err(s, constraint, [](auto&& solver, auto&& c) {
solver.addConstraint(Constraint(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_constraint(Solver& s, ConstraintData* constraint) {
return wrap_err(s, constraint, [](auto&& solver, auto&& c) {
solver.removeConstraint(Constraint(c));
});
}
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_add_edit_var(Solver& s, VariableData* var, double strength) {
return wrap_err(s, var, [strength](auto&& solver, auto&& v) {
solver.addEditVariable(Variable(v), strength);
});
}
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);
inline const KiwiErr* kiwi_solver_remove_edit_var(Solver& s, VariableData* var) {
return wrap_err(s, var, [](auto&& solver, auto&& v) {
solver.removeEditVariable(Variable(v));
});
}
inline const KiwiErr* kiwi_solver_suggest_value(Solver& s, VariableData* var, double value) {
return wrap_err(s, var, [value](auto&& solver, auto&& v) {
solver.suggestValue(Variable(v), value);
});
}

View File

@@ -1,9 +1,7 @@
#include "ljkiwi.hpp"
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include "luacompat.h"
#include "luakiwi-int.h"
namespace {
@@ -13,9 +11,7 @@ namespace {
enum TypeId { NOTYPE, VAR = 1, TERM, EXPR, CONSTRAINT, SOLVER, ERROR, NUMBER };
const int ERR_KIND_TAB = NUMBER + 1;
const int VAR_SUB_FN = ERR_KIND_TAB + 1;
const int CONTEXT_TAB_MAX = VAR_SUB_FN + 1;
enum { ERR_KIND_TAB = NUMBER + 1, VAR_SUB_FN, MEM_ERR_MSG, CONTEXT_TAB_MAX };
constexpr const char* const lkiwi_error_kinds[] = {
"KiwiErrNone",
@@ -146,8 +142,8 @@ inline void* try_type(lua_State* L, int idx, TypeId type_id) {
return lua_rawequal(L, -1, -2) ? p : 0;
}
inline Variable* try_var(lua_State* L, int idx) {
return static_cast<Variable*>(try_type(L, idx, VAR));
inline VariableData* try_var(lua_State* L, int idx) {
return *static_cast<VariableData**>(try_type(L, idx, VAR));
}
inline KiwiTerm* try_term(lua_State* L, int idx) {
@@ -191,8 +187,8 @@ inline void* try_arg(lua_State* L, int idx, TypeId* type_id, double* num) {
return 0;
}
inline Variable* get_var(lua_State* L, int idx) {
return static_cast<Variable*>(check_arg(L, idx, VAR));
inline VariableData* get_var(lua_State* L, int idx) {
return *static_cast<VariableData**>(check_arg(L, idx, VAR));
}
inline KiwiTerm* get_term(lua_State* L, int idx) {
@@ -210,24 +206,30 @@ inline KiwiExpression* get_expr_opt(lua_State* L, int idx) {
return static_cast<KiwiExpression*>(check_arg(L, idx, EXPR));
}
inline Constraint* get_constraint(lua_State* L, int idx) {
return static_cast<Constraint*>(check_arg(L, idx, CONSTRAINT));
inline ConstraintData* get_constraint(lua_State* L, int idx) {
return *static_cast<ConstraintData**>(check_arg(L, idx, CONSTRAINT));
}
inline KiwiSolver* get_solver(lua_State* L, int idx) {
return static_cast<KiwiSolver*>(check_arg(L, idx, SOLVER));
}
// note this expects the 2nd upvalue to have the variable weak table
template<typename... Args>
inline Variable* var_new(lua_State* L, Args&&... args) {
auto* var = new (lua_newuserdata(L, sizeof(Variable))) Variable(std::forward<Args>(args)...);
VariableData** var_new(lua_State* L) {
auto** varp = static_cast<VariableData**>(lua_newuserdata(L, sizeof(VariableData*)));
push_type(L, VAR);
lua_setmetatable(L, -2);
return varp;
}
// note this expects the 2nd upvalue to have the variable weak table
VariableData* var_register(lua_State* L, VariableData* var) {
if (lk_unlikely(!var)) {
lua_rawgeti(L, lua_upvalueindex(1), MEM_ERR_MSG);
lua_error(L);
}
#if defined(LUA_VERSION_NUM) && LUA_VERSION_NUM == 501
// a true compatibility shim has performance implications here
lua_pushlightuserdata(L, p);
lua_pushlightuserdata(L, var);
lua_pushvalue(L, -2);
lua_rawset(L, lua_upvalueindex(2));
#else
@@ -237,7 +239,7 @@ inline Variable* var_new(lua_State* L, Args&&... args) {
return var;
}
inline KiwiTerm* term_new(lua_State* L) {
KiwiTerm* term_new(lua_State* L) {
auto* term = static_cast<KiwiTerm*>(lua_newuserdata(L, sizeof(KiwiTerm)));
push_type(L, TERM);
lua_setmetatable(L, -2);
@@ -246,24 +248,30 @@ inline KiwiTerm* term_new(lua_State* L) {
inline KiwiExpression* expr_new(lua_State* L, int nterms) {
auto* expr = static_cast<KiwiExpression*>(lua_newuserdata(L, KiwiExpression::sz(nterms)));
expr->term_count = 0;
expr->owner = nullptr;
push_type(L, EXPR);
lua_setmetatable(L, -2);
return expr;
}
inline Constraint* constraint_new(
inline ConstraintData* constraint_new(
lua_State* L,
const KiwiExpression* lhs,
const KiwiExpression* rhs,
kiwi::RelationalOperator op,
double strength
) {
auto* c = kiwi_constraint_new(lhs, rhs, op, strength, lua_newuserdata(L, sizeof(Constraint)));
auto** c = static_cast<ConstraintData**>(lua_newuserdata(L, sizeof(ConstraintData*)));
push_type(L, CONSTRAINT);
lua_setmetatable(L, -2);
return c;
*c = kiwi_constraint_new(lhs, rhs, op, strength);
if (lk_unlikely(!*c)) {
lua_rawgeti(L, lua_upvalueindex(1), MEM_ERR_MSG);
lua_error(L);
}
return *c;
}
// stack disposition: dirty
@@ -288,7 +296,7 @@ KiwiExpression* toexpr(lua_State* L, int idx, KiwiExpression* temp) {
temp->term_count = 1;
push_type(L, VAR);
if (lua_rawequal(L, -1, -3)) {
temp->terms[0].var = static_cast<Variable*>(ud);
temp->terms[0].var = *static_cast<VariableData**>(ud);
temp->terms[0].coefficient = 1.0;
return temp;
}
@@ -332,7 +340,7 @@ inline int push_expr_one(lua_State* L, double constant, const KiwiTerm* term) {
expr->constant = constant;
expr->term_count = 1;
expr->terms[0].coefficient = term->coefficient;
expr->terms[0].var = kiwi_var_retain(term->var);
expr->terms[0].var = retain_unmanaged(term->var);
return 1;
}
@@ -341,20 +349,21 @@ inline int push_expr_pair(lua_State* L, double constant, const KiwiTerm* ta, con
e->constant = constant;
e->term_count = 2;
e->terms[0].coefficient = ta->coefficient;
e->terms[0].var = kiwi_var_retain(ta->var);
e->terms[0].var = retain_unmanaged(ta->var);
e->terms[1].coefficient = tb->coefficient;
e->terms[1].var = kiwi_var_retain(tb->var);
e->terms[1].var = retain_unmanaged(tb->var);
return 1;
}
inline int push_expr_var_term(lua_State* L, double constant, Variable* var, const KiwiTerm* t) {
inline int
push_expr_var_term(lua_State* L, double constant, VariableData* var, const KiwiTerm* t) {
auto* e = expr_new(L, 2);
e->constant = constant;
e->term_count = 2;
e->terms[0].coefficient = 1.0;
e->terms[0].var = kiwi_var_retain(var);
e->terms[0].var = retain_unmanaged(var);
e->terms[1].coefficient = t->coefficient;
e->terms[1].var = kiwi_var_retain(t->var);
e->terms[1].var = retain_unmanaged(t->var);
return 1;
}
@@ -365,23 +374,23 @@ int push_add_expr_term(lua_State* L, const KiwiExpression* expr, const KiwiTerm*
int i = 0;
for (; i < expr->term_count; ++i) {
e->terms[i].coefficient = expr->terms[i].coefficient;
e->terms[i].var = kiwi_var_retain(expr->terms[i].var);
e->terms[i].var = retain_unmanaged(expr->terms[i].var);
}
e->terms[i].coefficient = t->coefficient;
e->terms[i].var = kiwi_var_retain(t->var);
e->terms[i].var = retain_unmanaged(t->var);
return 1;
}
int lkiwi_var_m_add(lua_State* L) {
TypeId type_id_b;
double num;
double num = 0.0;
void* arg_b = try_arg(L, 2, &type_id_b, &num);
if (type_id_b == VAR) {
int isnum_a;
num = lua_tonumberx(L, 1, &isnum_a);
if (isnum_a) {
const KiwiTerm t {static_cast<Variable*>(arg_b), 1.0};
const KiwiTerm t {*static_cast<VariableData**>(arg_b), 1.0};
return push_expr_one(L, num, &t);
}
}
@@ -390,7 +399,7 @@ int lkiwi_var_m_add(lua_State* L) {
if (var_a) {
switch (type_id_b) {
case VAR: {
const KiwiTerm ta {var_a, 1.0}, tb {static_cast<Variable*>(arg_b), 1.0};
const KiwiTerm ta {var_a, 1.0}, tb {*static_cast<VariableData**>(arg_b), 1.0};
return push_expr_pair(L, 0.0, &ta, &tb);
}
case TERM:
@@ -436,7 +445,7 @@ int lkiwi_var_m_mul(lua_State* L) {
auto* var = try_var(L, varidx);
if (var) {
auto* term = term_new(L);
term->var = kiwi_var_retain(var);
term->var = retain_unmanaged(var);
term->coefficient = num;
return 1;
}
@@ -452,20 +461,20 @@ int lkiwi_var_m_div(lua_State* L) {
return op_error(L, "/", 1, 2);
}
auto* term = term_new(L);
term->var = kiwi_var_retain(var);
term->var = retain_unmanaged(var);
term->coefficient = 1.0 / num;
return 1;
}
int lkiwi_var_m_unm(lua_State* L) {
auto* term = term_new(L);
term->var = kiwi_var_retain(get_var(L, 1));
term->var = retain_unmanaged(get_var(L, 1));
term->coefficient = -1.0;
return 1;
}
int lkiwi_var_m_eq(lua_State* L) {
lua_pushboolean(L, get_var(L, 1)->equals(*get_var(L, 2)));
lua_pushboolean(L, get_var(L, 1) == get_var(L, 2));
return 1;
}
@@ -476,7 +485,7 @@ int lkiwi_var_m_tostring(lua_State* L) {
}
int lkiwi_var_m_gc(lua_State* L) {
get_var(L, 1)->~Variable();
release_unmanaged(get_var(L, 1));
return 0;
}
@@ -509,7 +518,7 @@ int lkiwi_var_toterm(lua_State* L) {
double coefficient = luaL_optnumber(L, 2, 1.0);
auto* term = term_new(L);
term->var = kiwi_var_retain(var);
term->var = retain_unmanaged(var);
term->coefficient = coefficient;
return 1;
@@ -538,59 +547,21 @@ constexpr const struct luaL_Reg kiwi_var_m[] = {
{"eq", lkiwi_eq},
{"le", lkiwi_le},
{"ge", lkiwi_ge},
{0, 0}};
{0, 0}
};
int lkiwi_var_new(lua_State* L) {
const char* name = luaL_optstring(L, 1, "");
var_new(L, name);
return 1;
}
int lkiwi_term_m_mul(lua_State* L) {
int isnum, termidx = 2;
double num = lua_tonumberx(L, 1, &isnum);
auto* varp = var_new(L);
var_register(L, *varp = make_unmanaged<VariableData>(name));
if (!isnum) {
termidx = 1;
num = lua_tonumberx(L, 2, &isnum);
}
if (isnum) {
const auto* term = try_term(L, termidx);
if (term) {
auto* ret = term_new(L);
ret->var = kiwi_var_retain(term->var);
ret->coefficient = term->coefficient * num;
return 1;
}
}
return op_error(L, "*", 1, 2);
}
int lkiwi_term_m_div(lua_State* L) {
const KiwiTerm* term = try_term(L, 1);
int isnum;
double num = lua_tonumberx(L, 2, &isnum);
if (!term || !isnum) {
return op_error(L, "/", 1, 2);
}
auto* ret = term_new(L);
ret->var = kiwi_var_retain(term->var);
ret->coefficient = term->coefficient / num;
return 1;
}
int lkiwi_term_m_unm(lua_State* L) {
const auto* term = get_term(L, 1);
auto* ret = term_new(L);
ret->var = kiwi_var_retain(term->var);
ret->coefficient = -term->coefficient;
return 1;
}
int lkiwi_term_m_add(lua_State* L) {
TypeId type_id_b;
double num;
double num = 0.0;
void* arg_b = try_arg(L, 2, &type_id_b, &num);
if (type_id_b == TERM) {
@@ -607,7 +578,7 @@ int lkiwi_term_m_add(lua_State* L) {
case TERM:
return push_expr_pair(L, 0.0, term_a, static_cast<KiwiTerm*>(arg_b));
case VAR: {
const KiwiTerm term_b {static_cast<Variable*>(arg_b), 1.0};
const KiwiTerm term_b {*static_cast<VariableData**>(arg_b), 1.0};
return push_expr_pair(L, 0.0, term_a, &term_b);
}
case EXPR:
@@ -628,6 +599,48 @@ int lkiwi_term_m_sub(lua_State* L) {
return 1;
}
int lkiwi_term_m_mul(lua_State* L) {
int isnum, termidx = 2;
double num = lua_tonumberx(L, 1, &isnum);
if (!isnum) {
termidx = 1;
num = lua_tonumberx(L, 2, &isnum);
}
if (isnum) {
const auto* term = try_term(L, termidx);
if (term) {
auto* ret = term_new(L);
ret->var = retain_unmanaged(term->var);
ret->coefficient = term->coefficient * num;
return 1;
}
}
return op_error(L, "*", 1, 2);
}
int lkiwi_term_m_div(lua_State* L) {
const KiwiTerm* term = try_term(L, 1);
int isnum;
double num = lua_tonumberx(L, 2, &isnum);
if (!term || !isnum) {
return op_error(L, "/", 1, 2);
}
auto* ret = term_new(L);
ret->var = retain_unmanaged(term->var);
ret->coefficient = term->coefficient / num;
return 1;
}
int lkiwi_term_m_unm(lua_State* L) {
const auto* term = get_term(L, 1);
auto* ret = term_new(L);
ret->var = retain_unmanaged(term->var);
ret->coefficient = -term->coefficient;
return 1;
}
int lkiwi_term_toexpr(lua_State* L) {
return push_expr_one(L, 0.0, get_term(L, 1));
}
@@ -645,7 +658,7 @@ int lkiwi_term_m_tostring(lua_State* L) {
}
int lkiwi_term_m_gc(lua_State* L) {
get_term(L, 1)->var->~Variable();
release_unmanaged(get_term(L, 1)->var);
return 0;
}
@@ -660,8 +673,10 @@ int lkiwi_term_m_index(lua_State* L) {
#else
lua_rawgetp(L, lua_upvalueindex(2), term->var);
#endif
if (lua_isnil(L, -1))
var_new(L, *term->var);
if (lua_isnil(L, -1)) {
auto* varp = var_new(L);
var_register(L, *varp = retain_unmanaged(term->var));
}
return 1;
} else if (len == 11 && memcmp("coefficient", k, len) == 0) {
lua_pushnumber(L, term->coefficient);
@@ -690,13 +705,14 @@ constexpr const struct luaL_Reg kiwi_term_m[] = {
{"eq", lkiwi_eq},
{"le", lkiwi_le},
{"ge", lkiwi_ge},
{0, 0}};
{0, 0}
};
int lkiwi_term_new(lua_State* L) {
auto* var = get_var(L, 1);
double coefficient = luaL_optnumber(L, 2, 1.0);
auto* term = term_new(L);
term->var = kiwi_var_retain(var);
term->var = retain_unmanaged(var);
term->coefficient = coefficient;
return 1;
}
@@ -704,7 +720,7 @@ int lkiwi_term_new(lua_State* L) {
int push_expr_constant(lua_State* L, const KiwiExpression* expr, double constant) {
auto* ne = expr_new(L, expr->term_count);
for (int i = 0; i < expr->term_count; i++) {
ne->terms[i].var = kiwi_var_retain(expr->terms[i].var);
ne->terms[i].var = retain_unmanaged(expr->terms[i].var);
ne->terms[i].coefficient = expr->terms[i].coefficient;
}
ne->constant = constant;
@@ -717,7 +733,7 @@ int push_mul_expr_coeff(lua_State* L, const KiwiExpression* expr, double coeff)
ne->constant = expr->constant * coeff;
ne->term_count = expr->term_count;
for (int i = 0; i < expr->term_count; i++) {
ne->terms[i].var = kiwi_var_retain(expr->terms[i].var);
ne->terms[i].var = retain_unmanaged(expr->terms[i].var);
ne->terms[i].coefficient = expr->terms[i].coefficient * coeff;
}
return 1;
@@ -731,16 +747,57 @@ int push_add_expr_expr(lua_State* L, const KiwiExpression* a, const KiwiExpressi
ne->term_count = na + nb;
for (int i = 0; i < na; i++) {
ne->terms[i].var = kiwi_var_retain(a->terms[i].var);
ne->terms[i].var = retain_unmanaged(a->terms[i].var);
ne->terms[i].coefficient = a->terms[i].coefficient;
}
for (int i = 0; i < nb; i++) {
ne->terms[i + na].var = kiwi_var_retain(b->terms[i].var);
ne->terms[i + na].var = retain_unmanaged(b->terms[i].var);
ne->terms[i + na].coefficient = b->terms[i].coefficient;
}
return 1;
}
int lkiwi_expr_m_add(lua_State* L) {
TypeId type_id_b;
double num = 0.0;
void* arg_b = try_arg(L, 2, &type_id_b, &num);
if (type_id_b == EXPR) {
int isnum_a;
num = lua_tonumberx(L, 1, &isnum_a);
if (isnum_a) {
auto* expr_b = static_cast<const KiwiExpression*>(arg_b);
return push_expr_constant(L, expr_b, num + expr_b->constant);
}
}
const auto* expr_a = try_expr(L, 1);
if (expr_a) {
switch (type_id_b) {
case EXPR:
return push_add_expr_expr(L, expr_a, static_cast<KiwiExpression*>(arg_b));
case TERM:
return push_add_expr_term(L, expr_a, static_cast<KiwiTerm*>(arg_b));
case VAR: {
const KiwiTerm term_b {*static_cast<VariableData**>(arg_b), 1.0};
return push_add_expr_term(L, expr_a, &term_b);
}
case NUMBER:
return push_expr_constant(L, expr_a, num + expr_a->constant);
default:
break;
}
}
return op_error(L, "+", 1, 2);
}
int lkiwi_expr_m_sub(lua_State* L) {
lua_settop(L, 2);
compat_arith_unm(L);
lkiwi_expr_m_add(L);
return 1;
}
int lkiwi_expr_m_mul(lua_State* L) {
int isnum, expridx = 2;
double num = lua_tonumberx(L, 1, &isnum);
@@ -773,47 +830,6 @@ int lkiwi_expr_m_unm(lua_State* L) {
return push_mul_expr_coeff(L, expr, -1.0);
}
int lkiwi_expr_m_add(lua_State* L) {
TypeId type_id_b;
double num;
void* arg_b = try_arg(L, 2, &type_id_b, &num);
if (type_id_b == EXPR) {
int isnum_a;
num = lua_tonumberx(L, 1, &isnum_a);
if (isnum_a) {
auto* expr_b = static_cast<const KiwiExpression*>(arg_b);
return push_expr_constant(L, expr_b, num + expr_b->constant);
}
}
const auto* expr_a = try_expr(L, 1);
if (expr_a) {
switch (type_id_b) {
case EXPR:
return push_add_expr_expr(L, expr_a, static_cast<KiwiExpression*>(arg_b));
case TERM:
return push_add_expr_term(L, expr_a, static_cast<KiwiTerm*>(arg_b));
case VAR: {
const KiwiTerm term_b {static_cast<Variable*>(arg_b), 1.0};
return push_add_expr_term(L, expr_a, &term_b);
}
case NUMBER:
return push_expr_constant(L, expr_a, num + expr_a->constant);
default:
break;
}
}
return op_error(L, "+", 1, 2);
}
int lkiwi_expr_m_sub(lua_State* L) {
lua_settop(L, 2);
compat_arith_unm(L);
lkiwi_expr_m_add(L);
return 1;
}
int lkiwi_expr_value(lua_State* L) {
const auto* expr = get_expr(L, 1);
double sum = expr->constant;
@@ -831,7 +847,7 @@ int lkiwi_expr_terms(lua_State* L) {
for (int i = 0; i < expr->term_count; i++) {
const auto* t = &expr->terms[i];
auto* new_term = term_new(L);
new_term->var = kiwi_var_retain(t->var);
new_term->var = retain_unmanaged(t->var);
new_term->coefficient = t->coefficient;
lua_rawseti(L, -2, i + 1);
}
@@ -865,10 +881,10 @@ int lkiwi_expr_m_tostring(lua_State* L) {
int lkiwi_expr_m_gc(lua_State* L) {
const auto* expr = get_expr(L, 1);
if (expr->owner) {
expr->owner->~Constraint();
release_unmanaged(expr->owner);
} else {
for (auto* t = expr->terms; t != expr->terms + expr->term_count; ++t) {
t->var->~Variable();
release_unmanaged(t->var);
}
}
return 0;
@@ -906,7 +922,8 @@ constexpr const struct luaL_Reg kiwi_expr_m[] = {
{"eq", lkiwi_eq},
{"le", lkiwi_le},
{"ge", lkiwi_ge},
{0, 0}};
{0, 0}
};
int lkiwi_expr_new(lua_State* L) {
int nterms = lua_gettop(L) - 1;
@@ -914,11 +931,10 @@ int lkiwi_expr_new(lua_State* L) {
auto* expr = expr_new(L, nterms);
expr->constant = constant;
expr->term_count = nterms;
for (int i = 0; i < nterms; i++) {
for (int i = 0; i < nterms; ++i, ++expr->term_count) {
const auto* term = get_term(L, i + 2);
expr->terms[i].var = kiwi_var_retain(term->var);
expr->terms[i].var = retain_unmanaged(term->var);
expr->terms[i].coefficient = term->coefficient;
}
return 1;
@@ -956,16 +972,17 @@ int lkiwi_constraint_expression(lua_State* L) {
auto* c = get_constraint(L, 1);
const auto& expr = c->expression();
const auto& terms = expr.terms();
const auto term_count = static_cast<int>(terms.size());
const auto term_count = static_cast<int>(terms.size() > INT_MAX ? INT_MAX : terms.size());
auto* ne = expr_new(L, term_count);
ne->owner = kiwi_constraint_retain(c);
ne->owner = retain_unmanaged(c);
ne->constant = expr.constant();
ne->term_count = term_count;
for (int i = 0; i < term_count; ++i) {
ne->terms[i].var = const_cast<Variable*>(&terms[i].variable());
ne->terms[i].coefficient = terms[i].coefficient();
const auto& t = terms[static_cast<std::size_t>(i)];
ne->terms[i].var = const_cast<Variable&>(t.variable()).ptr();
ne->terms[i].coefficient = t.coefficient();
}
return 1;
}
@@ -1025,7 +1042,7 @@ int lkiwi_constraint_m_tostring(lua_State* L) {
}
int lkiwi_constraint_m_gc(lua_State* L) {
get_constraint(L, 1)->~Constraint();
release_unmanaged(get_constraint(L, 1));
return 0;
}
@@ -1057,7 +1074,8 @@ constexpr const struct luaL_Reg kiwi_constraint_m[] = {
{"expression", lkiwi_constraint_expression},
{"add_to", lkiwi_constraint_add_to},
{"remove_from", lkiwi_constraint_remove_from},
{0, 0}};
{0, 0}
};
int lkiwi_constraint_new(lua_State* L) {
const auto* lhs = get_expr_opt(L, 1);
@@ -1071,9 +1089,9 @@ int lkiwi_constraint_new(lua_State* L) {
int push_pair_constraint(
lua_State* L,
Variable* left,
VariableData* left,
double coeff,
Variable* right,
VariableData* right,
double constant,
kiwi::RelationalOperator op,
double strength
@@ -1130,7 +1148,8 @@ constexpr const struct luaL_Reg lkiwi_constraints[] = {
{"pair_ratio", lkiwi_constraints_pair_ratio},
{"pair", lkiwi_constraints_pair},
{"single", lkiwi_constraints_single},
{0, 0}};
{0, 0}
};
void lkiwi_mod_constraints_new(lua_State* L, int ctx_i) {
luaL_newlibtable(L, lkiwi_constraints);
@@ -1181,10 +1200,7 @@ int lkiwi_error_m_tostring(lua_State* L) {
luaL_tolstring(L, -1, 0);
lua_remove(L, -2); // remove item
luaL_addvalue(&buf);
luaL_addstring(&buf, ")\n");
luaL_traceback(L, L, nullptr, 2);
luaL_addvalue(&buf);
luaL_addstring(&buf, ")");
luaL_pushresult(&buf);
return 1;
@@ -1192,7 +1208,8 @@ int lkiwi_error_m_tostring(lua_State* L) {
constexpr const struct luaL_Reg lkiwi_error_m[] = {
{"__tostring", lkiwi_error_m_tostring},
{0, 0}};
{0, 0}
};
int lkiwi_error_mask(lua_State* L) {
int invert = lua_toboolean(L, 2);
@@ -1238,21 +1255,21 @@ int lkiwi_solver_handle_err(lua_State* L, const KiwiErr* err, const KiwiSolver*
int lkiwi_solver_add_constraint(lua_State* L) {
auto* self = get_solver(L, 1);
const auto& c = *get_constraint(L, 2);
auto* c = get_constraint(L, 2);
auto* err = kiwi_solver_add_constraint(self->solver, c);
return lkiwi_solver_handle_err(L, err, self);
}
int lkiwi_solver_remove_constraint(lua_State* L) {
auto* self = get_solver(L, 1);
const auto& c = *get_constraint(L, 2);
auto* c = get_constraint(L, 2);
auto* err = kiwi_solver_remove_constraint(self->solver, c);
return lkiwi_solver_handle_err(L, err, self);
}
int lkiwi_solver_add_edit_var(lua_State* L) {
auto* self = get_solver(L, 1);
const auto& var = *get_var(L, 2);
auto* var = get_var(L, 2);
double strength = luaL_checknumber(L, 3);
auto* err = kiwi_solver_add_edit_var(self->solver, var, strength);
return lkiwi_solver_handle_err(L, err, self);
@@ -1260,14 +1277,14 @@ int lkiwi_solver_add_edit_var(lua_State* L) {
int lkiwi_solver_remove_edit_var(lua_State* L) {
auto* self = get_solver(L, 1);
const auto& var = *get_var(L, 2);
auto* var = get_var(L, 2);
auto* err = kiwi_solver_remove_edit_var(self->solver, var);
return lkiwi_solver_handle_err(L, err, self);
}
int lkiwi_solver_suggest_value(lua_State* L) {
auto* self = get_solver(L, 1);
const auto& var = *get_var(L, 2);
auto* var = get_var(L, 2);
double value = luaL_checknumber(L, 3);
auto* err = kiwi_solver_suggest_value(self->solver, var, value);
return lkiwi_solver_handle_err(L, err, self);
@@ -1285,15 +1302,15 @@ int lkiwi_solver_reset(lua_State* L) {
int lkiwi_solver_has_constraint(lua_State* L) {
auto* s = get_solver(L, 1);
const auto& c = *get_constraint(L, 2);
lua_pushboolean(L, s->solver.hasConstraint(c));
auto* c = get_constraint(L, 2);
lua_pushboolean(L, s->solver.hasConstraint(Constraint(c)));
return 1;
}
int lkiwi_solver_has_edit_var(lua_State* L) {
auto* s = get_solver(L, 1);
const auto& var = *get_var(L, 2);
lua_pushboolean(L, s->solver.hasEditVariable(var));
auto* var = get_var(L, 2);
lua_pushboolean(L, s->solver.hasEditVariable(Variable(var)));
return 1;
}
@@ -1338,26 +1355,26 @@ int lkiwi_add_remove_tab(lua_State* L, F&& fn) {
int lkiwi_solver_add_constraints(lua_State* L) {
return lkiwi_add_remove_tab(L, [](lua_State* L, KiwiSolver* s) {
return kiwi_solver_add_constraint(s->solver, *get_constraint(L, -1));
return kiwi_solver_add_constraint(s->solver, get_constraint(L, -1));
});
}
int lkiwi_solver_remove_constraints(lua_State* L) {
return lkiwi_add_remove_tab(L, [](lua_State* L, KiwiSolver* s) {
return kiwi_solver_add_constraint(s->solver, *get_constraint(L, -1));
return kiwi_solver_add_constraint(s->solver, get_constraint(L, -1));
});
}
int lkiwi_solver_add_edit_vars(lua_State* L) {
double strength = luaL_checknumber(L, 3);
return lkiwi_add_remove_tab(L, [strength](lua_State* L, KiwiSolver* s) {
return kiwi_solver_add_edit_var(s->solver, *get_var(L, -1), strength);
return kiwi_solver_add_edit_var(s->solver, get_var(L, -1), strength);
});
}
int lkiwi_solver_remove_edit_vars(lua_State* L) {
return lkiwi_add_remove_tab(L, [](lua_State* L, KiwiSolver* s) {
return kiwi_solver_remove_edit_var(s->solver, *get_var(L, -1));
return kiwi_solver_remove_edit_var(s->solver, get_var(L, -1));
});
}
@@ -1365,11 +1382,10 @@ int lkiwi_solver_suggest_values(lua_State* L) {
auto* self = get_solver(L, 1);
int narg = lua_gettop(L);
// block this particularly obnoxious case which is always a bug
// catch this obnoxious case which is always a bug
if (lua_type(L, 2) == LUA_TSTRING) {
luaL_typeerror(L, 2, "indexable");
}
// block this particularly obnoxious case which is always a bug
if (lua_type(L, 3) == LUA_TSTRING) {
luaL_typeerror(L, 3, "indexable");
}
@@ -1380,7 +1396,7 @@ int lkiwi_solver_suggest_values(lua_State* L) {
lua_geti(L, 3, i);
double value = luaL_checknumber(L, -1);
const KiwiErr* err = kiwi_solver_suggest_value(self->solver, *var, value);
const KiwiErr* err = kiwi_solver_suggest_value(self->solver, var, value);
if (err) {
error_new(L, err, 1, narg + 1 /* item_absi */);
unsigned error_mask = self->error_mask;
@@ -1445,7 +1461,8 @@ constexpr const struct luaL_Reg kiwi_solver_m[] = {
{"set_error_mask", lkiwi_solver_set_error_mask},
{"__tostring", lkiwi_solver_m_tostring},
{"__gc", lkiwi_solver_m_gc},
{0, 0}};
{0, 0}
};
int lkiwi_solver_new(lua_State* L) {
lua_Integer error_mask;
@@ -1542,7 +1559,8 @@ constexpr const struct luaL_Reg lkiwi[] = {
{"eq", lkiwi_eq},
{"le", lkiwi_le},
{"ge", lkiwi_ge},
{0, 0}};
{0, 0}
};
int no_member_mt_index(lua_State* L) {
luaL_error(L, "attempt to access non-existent member '%s'", lua_tostring(L, 2));
@@ -1608,6 +1626,8 @@ void compat_init(lua_State*, int) {}
#if defined __GNUC__ && (!defined _WIN32 || defined __CYGWIN__)
#define LJKIWI_EXPORT __attribute__((__visibility__("default")))
#elif defined _WIN32
#define LJKIWI_EXPORT __declspec(dllexport)
#endif
extern "C" LJKIWI_EXPORT int luaopen_ljkiwi(lua_State* L) {
@@ -1618,6 +1638,8 @@ extern "C" LJKIWI_EXPORT int luaopen_ljkiwi(lua_State* L) {
int ctx_i = lua_gettop(L);
compat_init(L, ctx_i);
lua_pushliteral(L, "kiwi library memory allocation error");
lua_rawseti(L, ctx_i, MEM_ERR_MSG);
no_member_mt_new(L);
register_type(L, "kiwi.Var", ctx_i, VAR, kiwi_var_m);

View File

@@ -0,0 +1,40 @@
rockspec_format = "3.0"
package = "kiwi"
version = "0.1.0-1"
source = {
url = "git+https://github.com/jkl1337/ljkiwi",
tag = "v0.1.0",
}
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 = {
LUAROCKS = "1",
LUA = "$(LUA)",
CFLAGS = "$(CFLAGS)",
LUA_INCDIR = "$(LUA_INCDIR)",
LUA_LIBDIR = "$(LUA_LIBDIR)",
LUALIB = "$(LUALIB)",
LIBFLAG = "$(LIBFLAG)",
LIB_EXT = "$(LIB_EXTENSION)",
OBJ_EXT = "$(OBJ_EXTENSION)",
},
install_variables = {
INST_LIBDIR = "$(LIBDIR)",
INST_LUADIR = "$(LUADIR)",
LIB_EXT = "$(LIB_EXTENSION)",
},
}

View File

@@ -0,0 +1,40 @@
rockspec_format = "3.0"
package = "kiwi"
version = "0.1.1-1"
source = {
url = "git+https://github.com/jkl1337/ljkiwi",
tag = "v0.1.1",
}
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 = {
LUAROCKS = "1",
LUA = "$(LUA)",
CFLAGS = "$(CFLAGS)",
LUA_INCDIR = "$(LUA_INCDIR)",
LUA_LIBDIR = "$(LUA_LIBDIR)",
LUALIB = "$(LUALIB)",
LIBFLAG = "$(LIBFLAG)",
LIB_EXT = "$(LIB_EXTENSION)",
OBJ_EXT = "$(OBJ_EXTENSION)",
},
install_variables = {
INST_LIBDIR = "$(LIBDIR)",
INST_LUADIR = "$(LUADIR)",
LIB_EXT = "$(LIB_EXTENSION)",
},
}

View File

@@ -21,9 +21,12 @@ dependencies = {
build = {
type = "make",
build_variables = {
LUAROCKS = "1",
LUA = "$(LUA)",
CFLAGS = "$(CFLAGS)",
LUA_INCDIR = "$(LUA_INCDIR)",
LUA_LIBDIR = "$(LUA_LIBDIR)",
LUALIB = "$(LUALIB)",
LIBFLAG = "$(LIBFLAG)",
LIB_EXT = "$(LIB_EXTENSION)",
OBJ_EXT = "$(OBJ_EXTENSION)",

240
spec/expression_spec.lua Normal file
View File

@@ -0,0 +1,240 @@
expose("module", function()
require("kiwi")
end)
describe("Expression", function()
local kiwi = require("kiwi")
local LUA_VERSION = tonumber(_VERSION:match("%d+%.%d+"))
it("construction", function()
local v = kiwi.Var("foo")
local v2 = kiwi.Var("bar")
local v3 = kiwi.Var("aux")
local e1 = kiwi.Expression(0, v * 1, v2 * 2, v3 * 3)
local e2 = kiwi.Expression(10, v * 1, v2 * 2, v3 * 3)
local constants = { 0, 10 }
for i, e in ipairs({ e1, e2 }) do
assert.equal(constants[i], e.constant)
local terms = e:terms()
assert.equal(3, #terms)
assert.equal(v, terms[1].var)
assert.equal(1.0, terms[1].coefficient)
assert.equal(v2, terms[2].var)
assert.equal(2.0, terms[2].coefficient)
assert.equal(v3, terms[3].var)
assert.equal(3.0, terms[3].coefficient)
end
if LUA_VERSION <= 5.2 then
assert.equal("1 foo + 2 bar + 3 aux + 10", tostring(e2))
else
assert.equal("1.0 foo + 2.0 bar + 3.0 aux + 10.0", tostring(e2))
end
assert.error(function()
kiwi.Expression(0, 0, v2 * 2, v3 * 3)
end)
end)
describe("method", function()
local v, t, e
before_each(function()
v = kiwi.Var("foo")
v:set(42)
t = kiwi.Term(v, 10)
e = t + 5
end)
it("has value", function()
v:set(42)
assert.equal(425, e:value())
v:set(87)
assert.equal(875, e:value())
end)
it("can be copied", function()
local e2 = e:copy()
assert.equal(e.constant, e2.constant)
local t1, t2 = e:terms(), e2:terms()
assert.equal(#t1, #t2)
for i = 1, #t1 do
assert.equal(t1[i].var, t2[i].var)
assert.equal(t1[i].coefficient, t2[i].coefficient)
end
end)
it("neg", function()
local neg = -e --[[@as kiwi.Expression]]
assert.True(kiwi.is_expression(neg))
local terms = neg:terms()
assert.equal(1, #terms)
assert.equal(v, terms[1].var)
assert.equal(-10.0, terms[1].coefficient)
assert.equal(-5, neg.constant)
end)
describe("bin op", function()
local v2, t2, e2
before_each(function()
v2 = kiwi.Var("bar")
t2 = kiwi.Term(v2)
e2 = v2 - 10
end)
it("mul", function()
for _, prod in ipairs({ e * 2.0, 2 * e }) do
assert.True(kiwi.is_expression(prod))
local terms = prod:terms()
assert.equal(1, #terms)
assert.equal(v, terms[1].var)
assert.equal(20.0, terms[1].coefficient)
assert.equal(10, prod.constant)
end
assert.error(function()
local _ = e * v
end)
end)
it("div", function()
local quot = e / 2.0
assert.True(kiwi.is_expression(quot))
local terms = quot:terms()
assert.equal(1, #terms)
assert.equal(v, terms[1].var)
assert.equal(5.0, terms[1].coefficient)
assert.equal(2.5, quot.constant)
assert.error(function()
local _ = e / v2
end)
end)
it("add", function()
for _, sum in ipairs({ e + 2.0, 2 + e }) do
assert.True(kiwi.is_expression(sum))
assert.equal(7.0, sum.constant)
local terms = sum:terms()
assert.equal(1, #terms)
assert.equal(10.0, terms[1].coefficient)
assert.equal(v, terms[1].var)
end
local sum = e + v2
assert.True(kiwi.is_expression(sum))
assert.equal(5, sum.constant)
local terms = sum:terms()
assert.equal(2, #terms)
assert.equal(v, terms[1].var)
assert.equal(10.0, terms[1].coefficient)
assert.equal(v2, terms[2].var)
assert.equal(1.0, terms[2].coefficient)
sum = e + t2
assert.True(kiwi.is_expression(sum))
assert.equal(5, sum.constant)
terms = sum:terms()
assert.equal(2, #terms)
assert.equal(v, terms[1].var)
assert.equal(10.0, terms[1].coefficient)
assert.equal(v2, terms[2].var)
assert.equal(1.0, terms[2].coefficient)
sum = e + e2
assert.True(kiwi.is_expression(sum))
assert.equal(-5, sum.constant)
terms = sum:terms()
assert.equal(2, #terms)
assert.equal(v, terms[1].var)
assert.equal(10.0, terms[1].coefficient)
assert.equal(v2, terms[2].var)
assert.equal(1.0, terms[2].coefficient)
assert.error(function()
local _ = t + "foo"
end)
assert.error(function()
local _ = t + {}
end)
end)
it("sub", function()
local constants = { 3, -3 }
for i, diff in ipairs({ e - 2.0, 2 - e }) 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 -10.0 or 10.0, terms[1].coefficient)
end
local diff = e - v2
assert.True(kiwi.is_expression(diff))
assert.equal(5, diff.constant)
local terms = diff:terms()
assert.equal(2, #terms)
assert.equal(v, terms[1].var)
assert.equal(10.0, terms[1].coefficient)
assert.equal(v2, terms[2].var)
assert.equal(-1.0, terms[2].coefficient)
diff = e - t2
assert.True(kiwi.is_expression(diff))
assert.equal(5, diff.constant)
terms = diff:terms()
assert.equal(2, #terms)
assert.equal(v, terms[1].var)
assert.equal(10.0, terms[1].coefficient)
assert.equal(v2, terms[2].var)
assert.equal(-1.0, terms[2].coefficient)
diff = e - e2
assert.True(kiwi.is_expression(diff))
assert.equal(15, diff.constant)
terms = diff:terms()
assert.equal(2, #terms)
assert.equal(v, terms[1].var)
assert.equal(10.0, terms[1].coefficient)
assert.equal(v2, terms[2].var)
assert.equal(-1.0, terms[2].coefficient)
assert.error(function()
local _ = e - "foo"
end)
assert.error(function()
local _ = e - {}
end)
end)
it("constraint expr op expr", function()
local ops = { "LE", "EQ", "GE" }
for i, meth in ipairs({ "le", "eq", "ge" }) do
local c = e[meth](e, e2)
assert.True(kiwi.is_constraint(c))
local expr = c:expression()
local terms = expr:terms()
assert.equal(2, #terms)
-- order can be randomized due to use of map
if terms[1].var ~= v then
terms[1], terms[2] = terms[2], terms[1]
end
assert.equal(v, terms[1].var)
assert.equal(10.0, terms[1].coefficient)
assert.equal(v2, terms[2].var)
assert.equal(-1.0, terms[2].coefficient)
assert.equal(15, expr.constant)
assert.equal(ops[i], c:op())
assert.equal(kiwi.strength.REQUIRED, c:strength())
end
end)
end)
end)
end)

245
spec/term_spec.lua Normal file
View File

@@ -0,0 +1,245 @@
expose("module", function()
require("kiwi")
end)
describe("Term", function()
local kiwi = require("kiwi")
local LUA_VERSION = tonumber(_VERSION:match("%d+%.%d+"))
it("construction", function()
local v = kiwi.Var("foo")
local t = kiwi.Term(v)
assert.equal(v, t.var)
assert.equal(1.0, t.coefficient)
t = kiwi.Term(v, 100)
assert.equal(v, t.var)
assert.equal(100, t.coefficient)
if LUA_VERSION <= 5.2 then
assert.equal("100 foo", tostring(t))
else
assert.equal("100.0 foo", tostring(t))
end
assert.error(function()
kiwi.Term("")
end)
end)
describe("method", function()
local v, v2, t, t2
before_each(function()
v = kiwi.Var("foo")
t = kiwi.Term(v, 10)
end)
it("has value", function()
v:set(42)
assert.equal(420, t:value())
v:set(87)
assert.equal(870, t:value())
end)
it("has toexpr", function()
local e = t:toexpr()
assert.True(kiwi.is_expression(e))
assert.equal(0, e.constant)
local terms = e:terms()
assert.equal(1, #terms)
assert.equal(v, terms[1].var)
assert.equal(10.0, terms[1].coefficient)
end)
it("neg", function()
local neg = -t --[[@as kiwi.Term]]
assert.True(kiwi.is_term(neg))
assert.equal(v, neg.var)
assert.equal(-10, neg.coefficient)
end)
describe("bin op", function()
before_each(function()
v2 = kiwi.Var("bar")
t2 = kiwi.Term(v2)
end)
it("mul", function()
for _, prod in ipairs({ t * 2.0, 2 * t }) do
assert.True(kiwi.is_term(prod))
assert.equal(v, prod.var)
assert.equal(20, prod.coefficient)
end
assert.error(function()
local _ = t * v
end)
end)
it("div", function()
local quot = t / 2.0
assert.True(kiwi.is_term(quot))
assert.equal(v, quot.var)
assert.equal(5.0, quot.coefficient)
assert.error(function()
local _ = v / v2
end)
end)
it("add", function()
for _, sum in ipairs({ t + 2.0, 2 + t }) do
assert.True(kiwi.is_expression(sum))
assert.equal(2.0, sum.constant)
local terms = sum:terms()
assert.equal(1, #terms)
assert.equal(10.0, terms[1].coefficient)
assert.equal(v, terms[1].var)
end
local sum = t + 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(10.0, terms[1].coefficient)
assert.equal(v2, terms[2].var)
assert.equal(1.0, terms[2].coefficient)
sum = t + t2
assert.True(kiwi.is_expression(sum))
assert.equal(0, sum.constant)
terms = sum:terms()
assert.equal(2, #terms)
assert.equal(v, terms[1].var)
assert.equal(10.0, terms[1].coefficient)
assert.equal(v2, terms[2].var)
assert.equal(1.0, terms[2].coefficient)
local t3 = kiwi.Term(v2, 20)
sum = t3 + sum
assert.True(kiwi.is_expression(sum))
assert.equal(0, sum.constant)
terms = sum:terms()
assert.equal(3, #terms)
assert.equal(v, terms[1].var)
assert.equal(10.0, terms[1].coefficient)
assert.equal(v2, terms[2].var)
assert.equal(1.0, terms[2].coefficient)
assert.equal(v2, terms[3].var)
assert.equal(20.0, terms[3].coefficient)
assert.error(function()
local _ = t + "foo"
end)
assert.error(function()
local _ = t + {}
end)
end)
it("sub", function()
local constants = { -2, 2 }
for i, diff in ipairs({ t - 2.0, 2 - t }) 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 10.0 or -10.0, terms[1].coefficient)
end
local diff = t - 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(10.0, terms[1].coefficient)
assert.equal(v2, terms[2].var)
assert.equal(-1.0, terms[2].coefficient)
diff = t - t2
assert.True(kiwi.is_expression(diff))
assert.equal(0, diff.constant)
terms = diff:terms()
assert.equal(2, #terms)
assert.equal(v, terms[1].var)
assert.equal(10.0, terms[1].coefficient)
assert.equal(v2, terms[2].var)
assert.equal(-1.0, terms[2].coefficient)
local t3 = kiwi.Term(v2, 20)
diff = t3 - diff
assert.True(kiwi.is_expression(diff))
assert.equal(0, diff.constant)
terms = diff:terms()
assert.equal(3, #terms)
assert.equal(v, terms[1].var)
assert.equal(-10.0, terms[1].coefficient)
assert.equal(v2, terms[2].var)
assert.equal(1.0, terms[2].coefficient)
assert.equal(v2, terms[3].var)
assert.equal(20.0, terms[3].coefficient)
assert.error(function()
local _ = t - "foo"
end)
assert.error(function()
local _ = t - {}
end)
end)
it("constraint term op expr", function()
local ops = { "LE", "EQ", "GE" }
for i, meth in ipairs({ "le", "eq", "ge" }) do
local c = t[meth](t, v2 + 1)
assert.True(kiwi.is_constraint(c))
local e = c:expression()
local terms = e:terms()
assert.equal(2, #terms)
-- order can be randomized due to use of map
if terms[1].var ~= v then
terms[1], terms[2] = terms[2], terms[1]
end
assert.equal(v, terms[1].var)
assert.equal(10.0, terms[1].coefficient)
assert.equal(v2, terms[2].var)
assert.equal(-1.0, terms[2].coefficient)
assert.equal(-1, e.constant)
assert.equal(ops[i], c:op())
assert.equal(kiwi.strength.REQUIRED, c:strength())
end
end)
it("constraint term op term", function()
for i, meth in ipairs({ "le", "eq", "ge" }) do
local c = t[meth](t, t2)
assert.True(kiwi.is_constraint(c))
local e = c:expression()
local terms = e:terms()
assert.equal(2, #terms)
-- order can be randomized due to use of map
if terms[1].var ~= v then
terms[1], terms[2] = terms[2], terms[1]
end
assert.equal(v, terms[1].var)
assert.equal(10.0, terms[1].coefficient)
assert.equal(v2, terms[2].var)
assert.equal(-1.0, terms[2].coefficient)
assert.equal(0, e.constant)
end
end)
end)
end)
end)

View File

@@ -127,6 +127,8 @@ describe("Var", function()
assert.equal(v2, terms[2].var)
assert.equal(-1.0, terms[2].coefficient)
-- TODO: terms and expressions
assert.error(function()
local _ = v - "foo"
end)

32
t.lua
View File

@@ -1,32 +0,0 @@
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

View File

@@ -1,204 +0,0 @@
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")

View File

@@ -1,90 +0,0 @@
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

View File

@@ -1,28 +0,0 @@
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()

View File

@@ -1,49 +0,0 @@
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)

View File

@@ -1,37 +0,0 @@
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)

View File

@@ -1,41 +0,0 @@
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

View File

@@ -1,26 +0,0 @@
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)

View File

@@ -1,66 +0,0 @@
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())