28 Commits

Author SHA1 Message Date
f6bf09a3eb huge 2024-02-24 02:43:11 -06:00
579d671a77 Update rockspec 2024-02-23 13:07:02 -06:00
61ba76c5a3 Cleanup 2024-02-23 12:56:04 -06:00
8854c0edbe add build.bat 2024-02-22 23:23:07 -06:00
00a9fda814 again 2024-02-22 22:57:27 -06:00
a8c1a10cab fix MSVC warnings 2024-02-22 22:40:53 -06:00
2a71914ed8 ok 2024-02-22 20:58:47 -06:00
dea448e46b better 2024-02-22 18:23:33 -06:00
3811d82212 skin this cat 2024-02-22 16:02:31 -06:00
2baee14c5d misc Lua Version difference fixes 2024-02-22 11:15:17 -06:00
198265ee36 get compiling with c++ 2024-02-22 10:15:01 -06:00
604e3df41f just starting 2024-02-22 03:54:51 -06:00
6a99504835 Minor fixes and API change
- fix stupid spaces in Makefile
- small type annotation fix
- Modify Expression constructor API to use varargs
- Update kiwi.lua to support future C API module
2024-02-21 22:14:24 -06:00
9b00e62d43 update github workflow 2024-02-19 21:31:59 -06:00
3631704544 update LICENSE 2024-02-19 09:20:46 -06:00
17f67b8879 use our own lua gh action 2024-02-18 00:20:41 -06:00
d83cc3468c Drop LuaJIT 2.0.5 support
Primary CI target is luajit-openresty. Even 2.1.0-beta3 is ancient.
2024-02-17 22:36:28 -06:00
b3cf6136a4 Convert enum values to number for LuaJIT 2.0.5 compat 2024-02-17 22:07:01 -06:00
2ffc5a333b All kinds of things
- Add modified BSD license
- Initial test runner github action
- Add is_{type} functions
- export kiwi.Error metatable such that it appears like a class.
- Allow Expression.new to take nil for terms
- Initial set of specs
2024-02-17 21:58:13 -06:00
d85796a038 More flexible error handling, convenience methods
Allow getting solver errors returned rather than raising error.
The API allows setting a mask of which error kinds raise vs return.

Also add some convenience methods:
- `add_to` and `remove_from` constraint methods to solver.
- add and remove multiple constraints and edit/suggest variables at once
2024-02-17 01:08:45 -06:00
37833f7b2b Remove now unnecessary const_casts. 2024-02-17 01:08:45 -06:00
59465d3142 Add const to Solver dump methods and Variable equals. 2024-02-17 01:08:29 -06:00
8b57e0c441 Use global scratch space for temporary objects, better errors 2024-02-15 16:23:57 -06:00
bc948d1a61 Restyle and limit scopes for quicker navigation 2024-02-14 16:00:49 -06:00
3311b582a1 Refactors to produce slightly less garbage 2024-02-14 13:53:14 -06:00
84a01179cd Manage GC data better
Make sure that all cdata references for variables is tracked. This is
done by attaching finalizers to Expression and Term objects. Some
effort is made to avoid creating tracked temporary objects (esp Terms)
now.
2024-02-14 01:42:56 -06:00
e43272487f Guard against most egregious mistakes in calling the library
LuaJIT FFI is not inherently memory safe and there is no way
to completely guard against the caller doing something that
will trample over memory, but we can get pretty close. Biggest
issue is that an empty table will stand-in for a ref struct with
a null ref. So check for that in all the calls. In the calls that
raise errors we now have a specific error for it. In the other
functions the "nil" object is handled quietly but without a nullptr
dereference and hopefully no UB.
2024-02-13 16:58:59 -06:00
59bdeedc18 Initial actual commit, awaiting CI 2024-02-12 18:05:41 -06:00
32 changed files with 1279 additions and 1477 deletions

View File

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

View File

@@ -1,4 +1,3 @@
---
name: Busted name: Busted
on: [push, pull_request] on: [push, pull_request]
@@ -8,25 +7,13 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
lua_version: lua_version: ["luajit-openresty", "luajit-2.1.0-beta3", "luajit-git"]
[
"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: ${{ matrix.os }} runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- uses: ilammy/msvc-dev-cmd@v1
if: ${{ !startsWith(matrix.lua_version, 'luajit-') }}
- name: Setup lua - name: Setup lua
uses: jkl1337/gh-actions-lua@master uses: jkl1337/gh-actions-lua@master
with: with:
@@ -36,75 +23,14 @@ jobs:
- name: Setup dependencies - name: Setup dependencies
run: | run: |
luarocks install busted luarocks install busted
luarocks install luacov-reporter-lcov luarocks install luacov-coveralls
- name: Build C++ library - name: Build C library
run: | run: make
luarocks make --no-install
env:
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 - name: Run busted tests
run: | run: busted -c -v
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 - name: Report test coverage
if: |- if: success()
success() && !startsWith(matrix.os, 'windows-')
&& (startsWith(matrix.lua_version, 'luajit-') || startsWith(matrix.os, 'ubuntu-'))
continue-on-error: true continue-on-error: true
uses: coverallsapp/github-action@v2 run: luacov-coveralls -e .luarocks -e spec
with: env:
flag-name: run ${{ join(matrix.*, ' - ') }} COVERALLS_REPO_TOKEN: ${{ github.token }}
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,20 +2,9 @@
/lua /lua
/lua_modules /lua_modules
/.luarocks /.luarocks
/config.mk
*.gcda
*.gcno
*.gcov
*.lcov
*.out
*.pch *.pch
*.gch *.gch
*.lib
*.so *.so
*.o *.o
*.obj
*.exp
*.dll
*.rock
.cache/ .cache/
compile_commands.json compile_commands.json

View File

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

156
Makefile
View File

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

View File

@@ -1,21 +0,0 @@
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,20 +1,21 @@
ljkiwi - Free LuaJIT FFI and Lua C API kiwi (Cassowary derived) constraint solver. ljkiwi - Free LuaJIT FFI 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) [![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) [![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/kiwi)](https://luarocks.org/modules/jkl/kiwi) [![luarocks](https://img.shields.io/luarocks/v/jkl/ljkiwi)](https://luarocks.org/modules/jkl/ljkiwi)
# Introduction # 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. 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. 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 and they appear mostly unmaintained. 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, 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 Since the C++ Kiwi library is well tested and widely used it was simpler to provide a LuaJIT FFI wrapper and use that.
in most common use cases all the heavy lifting is done in the library, there is usually This package has no dependencies other than a C++14 toolchain to compile the included Kiwi library and a small C wrapper.
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 ## Example
@@ -90,4 +91,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. 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 ## Documentation
The API is fully annotated and will work with lua-language-server. Documentation can also be generated with lua-language-server. WIP - However the API is fully annotated and will work with lua-language-server. Documentation can also be generated with lua-language-server.

2
build.bat Normal file
View File

@@ -0,0 +1,2 @@
cl /nologo /O2 /W4 /wd4200 /c /D_CRT_SECURE_NO_DEPRECATE /I "C:\Program Files\luarocks\include" /EHs /I kiwi luakiwi\luakiwi.cpp
link /DLL /out:ckiwi.dll /def:luakiwi.def /LIBPATH:"C:\Program Files\luarocks\lib" luakiwi.obj lua54.lib

View File

@@ -1,12 +1,10 @@
#include "ljkiwi.hpp"
#include "ckiwi.h" #include "ckiwi.h"
#include <kiwi/kiwi.h> #include <kiwi/kiwi.h>
#include <climits>
#include <cstdarg>
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>
#include <string>
#if defined(__GNUC__) && !defined(LJKIWI_NO_BUILTIN) #if defined(__GNUC__) && !defined(LJKIWI_NO_BUILTIN)
#define lk_likely(x) (__builtin_expect(((x) != 0), 1)) #define lk_likely(x) (__builtin_expect(((x) != 0), 1))
@@ -38,69 +36,59 @@ const KiwiErr* new_error(const KiwiErr* base, const std::exception& ex) {
static const constexpr KiwiErr kKiwiErrUnhandledCxxException { static const constexpr KiwiErr kKiwiErrUnhandledCxxException {
KiwiErrUnknown, KiwiErrUnknown,
"An unhandled C++ exception occurred." "An unhandled C++ exception occurred."};
};
static const constexpr KiwiErr kKiwiErrNullObjectArg0 { static const constexpr KiwiErr kKiwiErrNullObjectArg0 {
KiwiErrNullObject, KiwiErrNullObject,
"null object passed as argument #0 (self)" "null object passed as argument #0 (self)"};
};
static const constexpr KiwiErr kKiwiErrNullObjectArg1 { static const constexpr KiwiErr kKiwiErrNullObjectArg1 {
KiwiErrNullObject, KiwiErrNullObject,
"null object passed as argument #1" "null object passed as argument #1"};
};
template<typename F> template<typename F>
const KiwiErr* wrap_err(F&& f) { inline const KiwiErr* wrap_err(F&& f) {
try { try {
f(); f();
} catch (const UnsatisfiableConstraint& ex) { } catch (const UnsatisfiableConstraint& ex) {
static const constexpr KiwiErr err { static const constexpr KiwiErr err {
KiwiErrUnsatisfiableConstraint, KiwiErrUnsatisfiableConstraint,
"The constraint cannot be satisfied." "The constraint cannot be satisfied."};
};
return &err; return &err;
} catch (const UnknownConstraint& ex) { } catch (const UnknownConstraint& ex) {
static const constexpr KiwiErr err { static const constexpr KiwiErr err {
KiwiErrUnknownConstraint, KiwiErrUnknownConstraint,
"The constraint has not been added to the solver." "The constraint has not been added to the solver."};
};
return &err; return &err;
} catch (const DuplicateConstraint& ex) { } catch (const DuplicateConstraint& ex) {
static const constexpr KiwiErr err { static const constexpr KiwiErr err {
KiwiErrDuplicateConstraint, KiwiErrDuplicateConstraint,
"The constraint has already been added to the solver." "The constraint has already been added to the solver."};
};
return &err; return &err;
} catch (const UnknownEditVariable& ex) { } catch (const UnknownEditVariable& ex) {
static const constexpr KiwiErr err { static const constexpr KiwiErr err {
KiwiErrUnknownEditVariable, KiwiErrUnknownEditVariable,
"The edit variable has not been added to the solver." "The edit variable has not been added to the solver."};
};
return &err; return &err;
} catch (const DuplicateEditVariable& ex) { } catch (const DuplicateEditVariable& ex) {
static const constexpr KiwiErr err { static const constexpr KiwiErr err {
KiwiErrDuplicateEditVariable, KiwiErrDuplicateEditVariable,
"The edit variable has already been added to the solver." "The edit variable has already been added to the solver."};
};
return &err; return &err;
} catch (const BadRequiredStrength& ex) { } catch (const BadRequiredStrength& ex) {
static const constexpr KiwiErr err { static const constexpr KiwiErr err {
KiwiErrBadRequiredStrength, KiwiErrBadRequiredStrength,
"A required strength cannot be used in this context." "A required strength cannot be used in this context."};
};
return &err; return &err;
} catch (const InternalSolverError& ex) { } catch (const InternalSolverError& ex) {
static const constexpr KiwiErr base { static const constexpr KiwiErr base {
KiwiErrInternalSolverError, KiwiErrInternalSolverError,
"An internal solver error occurred." "An internal solver error occurred."};
};
return new_error(&base, ex); return new_error(&base, ex);
} catch (std::bad_alloc&) { } catch (std::bad_alloc&) {
static const constexpr KiwiErr err {KiwiErrAlloc, "A memory allocation failed."}; static const constexpr KiwiErr err {KiwiErrAlloc, "A memory allocation failed."};
@@ -114,7 +102,7 @@ const KiwiErr* wrap_err(F&& f) {
} }
template<typename P, typename R, typename F> template<typename P, typename R, typename F>
const KiwiErr* wrap_err(P self, F&& f) { inline const KiwiErr* wrap_err(P self, F&& f) {
if (lk_unlikely(!self)) { if (lk_unlikely(!self)) {
return &kKiwiErrNullObjectArg0; return &kKiwiErrNullObjectArg0;
} }
@@ -122,51 +110,36 @@ const KiwiErr* wrap_err(P self, F&& f) {
} }
template<typename P, typename R, typename F> template<typename P, typename R, typename F>
const KiwiErr* wrap_err(P* self, R* item, F&& f) { inline const KiwiErr* wrap_err(P self, R item, F&& f) {
if (lk_unlikely(!self)) { if (lk_unlikely(!self)) {
return &kKiwiErrNullObjectArg0; return &kKiwiErrNullObjectArg0;
} else if (lk_unlikely(!item)) { } else if (lk_unlikely(!item)) {
return &kKiwiErrNullObjectArg1; 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 } // namespace
extern "C" { extern "C" {
KiwiVar* kiwi_var_construct(const char* name) { KiwiTypeInfo kiwi_ti_KiwiVar = {sizeof(KiwiVar), alignof(KiwiVar)};
return make_unmanaged<VariableData>(lk_likely(name) ? name : ""); KiwiTypeInfo kiwi_ti_KiwiConstraint = {sizeof(KiwiConstraint), alignof(KiwiConstraint)};
void kiwi_var_construct(const char* name, void* mem) {
new (mem) KiwiVar {lk_likely(name) ? name : ""};
} }
void kiwi_var_release(KiwiVar* var) { void kiwi_var_release(KiwiVar* var) {
release_unmanaged(var); if (lk_likely(var))
var->~KiwiVar();
} }
void kiwi_var_retain(KiwiVar* var) { void kiwi_var_retain(KiwiVar* var) {
retain_unmanaged(var); if (lk_likely(var)) {
alignas(KiwiVar) unsigned char buf[sizeof(KiwiVar)];
new (buf) KiwiVar(*var);
}
} }
const char* kiwi_var_name(const KiwiVar* var) { const char* kiwi_var_name(const KiwiVar* var) {
@@ -174,7 +147,7 @@ const char* kiwi_var_name(const KiwiVar* var) {
} }
void kiwi_var_set_name(KiwiVar* var, const char* name) { void kiwi_var_set_name(KiwiVar* var, const char* name) {
if (lk_likely(var && name)) if (lk_likely(var))
var->setName(name); var->setName(name);
} }
@@ -187,60 +160,60 @@ void kiwi_var_set_value(KiwiVar* var, double value) {
var->setValue(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) { void kiwi_expression_retain(KiwiExpression* expr) {
if (lk_unlikely(!expr)) if (lk_unlikely(!expr))
return; return;
alignas(KiwiVar) unsigned char buf[sizeof(KiwiVar)];
for (auto* t = expr->terms_; t != expr->terms_ + expr->term_count; ++t) { for (auto* t = expr->terms_; t != expr->terms_ + expr->term_count; ++t) {
retain_unmanaged(t->var); new (buf) KiwiVar(*t->var);
} }
expr->owner = expr;
} }
void kiwi_expression_destroy(KiwiExpression* expr) { void kiwi_expression_destroy(KiwiExpression* expr) {
if (lk_unlikely(!expr || !expr->owner)) if (lk_unlikely(!expr))
return; return;
if (expr->owner == expr) { if (expr->owner) {
for (auto* t = expr->terms_; t != expr->terms_ + expr->term_count; ++t) { expr->owner->~KiwiConstraint();
release_unmanaged(t->var);
}
} else { } else {
release_unmanaged(static_cast<ConstraintData*>(expr->owner)); for (auto* t = expr->terms_; t != expr->terms_ + expr->term_count; ++t) {
t->var->~KiwiVar();
}
} }
} }
KiwiConstraint* kiwi_constraint_construct( void kiwi_constraint_construct(
const KiwiExpression* lhs, const KiwiExpression* lhs,
const KiwiExpression* rhs, const KiwiExpression* rhs,
enum KiwiRelOp op, enum KiwiRelOp op,
double strength double strength,
void* mem
) { ) {
if (strength < 0.0) { if (strength < 0.0) {
strength = kiwi::strength::required; strength = kiwi::strength::required;
} }
std::vector<Term> terms; std::vector<Term> terms;
terms.reserve(static_cast<decltype(terms)::size_type>( terms.reserve((lhs ? lhs->term_count : 0) + (rhs ? rhs->term_count : 0));
(lhs && lhs->term_count > 0 ? lhs->term_count : 0)
+ (rhs && rhs->term_count > 0 ? rhs->term_count : 0)
));
if (lhs) { if (lhs) {
for (int i = 0; i < lhs->term_count; ++i) { for (auto* t = lhs->terms_; t != lhs->terms_ + lhs->term_count; ++t) {
const auto& t = lhs->terms_[i]; if (t->var)
if (t.var) terms.emplace_back(*t->var, t->coefficient);
terms.emplace_back(Variable(t.var), t.coefficient);
} }
} }
if (rhs) { if (rhs) {
for (int i = 0; i < rhs->term_count; ++i) { for (auto* t = rhs->terms_; t != rhs->terms_ + rhs->term_count; ++t) {
const auto& t = rhs->terms_[i]; if (t->var)
if (t.var) terms.emplace_back(*t->var, -t->coefficient);
terms.emplace_back(Variable(t.var), -t.coefficient);
} }
} }
return make_unmanaged<ConstraintData>( new (mem) Constraint(
Expression(std::move(terms), (lhs ? lhs->constant : 0.0) - (rhs ? rhs->constant : 0.0)), Expression(std::move(terms), (lhs ? lhs->constant : 0.0) - (rhs ? rhs->constant : 0.0)),
static_cast<RelationalOperator>(op), static_cast<RelationalOperator>(op),
strength strength
@@ -248,11 +221,15 @@ KiwiConstraint* kiwi_constraint_construct(
} }
void kiwi_constraint_release(KiwiConstraint* c) { void kiwi_constraint_release(KiwiConstraint* c) {
release_unmanaged(c); if (lk_likely(c))
c->~KiwiConstraint();
} }
void kiwi_constraint_retain(KiwiConstraint* c) { void kiwi_constraint_retain(KiwiConstraint* c) {
retain_unmanaged(c); if (lk_likely(c)) {
alignas(KiwiConstraint) unsigned char buf[sizeof(KiwiConstraint)];
new (buf) KiwiConstraint(*c);
}
} }
double kiwi_constraint_strength(const KiwiConstraint* c) { double kiwi_constraint_strength(const KiwiConstraint* c) {
@@ -273,20 +250,20 @@ int kiwi_constraint_expression(KiwiConstraint* c, KiwiExpression* out, int out_s
const auto& expr = c->expression(); const auto& expr = c->expression();
const auto& terms = expr.terms(); const auto& terms = expr.terms();
int n = terms.size() < INT_MAX ? static_cast<int>(terms.size()) : INT_MAX; const auto term_count = static_cast<int>(terms.size());
if (!out || out_size < n) if (!out || out_size < term_count)
return n; return term_count;
for (int i = 0; i < n; ++i) { for (int i = 0; i < term_count; ++i) {
const auto& t = terms[static_cast<std::size_t>(i)]; out->terms_[i].var = const_cast<KiwiVar*>(&terms[i].variable());
out->terms_[i].var = const_cast<Variable&>(t.variable()).ptr(); out->terms_[i].coefficient = terms[i].coefficient();
out->terms_[i].coefficient = t.coefficient();
} }
out->constant = expr.constant(); out->constant = expr.constant();
out->term_count = n; out->term_count = term_count;
out->owner = retain_unmanaged(c); out->owner = c;
kiwi_constraint_retain(c);
return n; return term_count;
} }
struct KiwiSolver { struct KiwiSolver {
@@ -294,13 +271,15 @@ struct KiwiSolver {
Solver solver; Solver solver;
}; };
KiwiSolver* kiwi_solver_construct(unsigned error_mask) { KiwiTypeInfo kiwi_ti_KiwiSolver = {sizeof(KiwiSolver), alignof(KiwiSolver)};
return new KiwiSolver {error_mask};
void kiwi_solver_construct(unsigned error_mask, void* mem) {
new (mem) KiwiSolver {error_mask};
} }
void kiwi_solver_destroy(KiwiSolver* s) { void kiwi_solver_destroy(KiwiSolver* s) {
if (lk_likely(s)) if (lk_likely(s))
delete s; s->~KiwiSolver();
} }
unsigned kiwi_solver_get_error_mask(const KiwiSolver* s) { unsigned kiwi_solver_get_error_mask(const KiwiSolver* s) {
@@ -312,38 +291,38 @@ void kiwi_solver_set_error_mask(KiwiSolver* s, unsigned mask) {
s->error_mask = mask; s->error_mask = mask;
} }
const KiwiErr* kiwi_solver_add_constraint(KiwiSolver* s, KiwiConstraint* constraint) { const KiwiErr* kiwi_solver_add_constraint(KiwiSolver* s, const KiwiConstraint* constraint) {
return wrap_err(s, constraint, [](auto&& s, auto&& c) { s.addConstraint(Constraint(c)); }); return wrap_err(s, constraint, [](auto& s, const auto& c) { s.addConstraint(c); });
} }
const KiwiErr* kiwi_solver_remove_constraint(KiwiSolver* s, KiwiConstraint* constraint) { const KiwiErr* kiwi_solver_remove_constraint(KiwiSolver* s, const KiwiConstraint* constraint) {
return wrap_err(s, constraint, [](auto&& s, auto&& c) { s.removeConstraint(Constraint(c)); }); return wrap_err(s, constraint, [](auto& s, const auto& c) { s.removeConstraint(c); });
} }
bool kiwi_solver_has_constraint(const KiwiSolver* s, KiwiConstraint* constraint) { bool kiwi_solver_has_constraint(const KiwiSolver* s, const KiwiConstraint* constraint) {
if (lk_unlikely(!s || !constraint)) if (lk_unlikely(!s || !constraint))
return 0; return 0;
return s->solver.hasConstraint(Constraint(constraint)); return s->solver.hasConstraint(*constraint);
} }
const KiwiErr* kiwi_solver_add_edit_var(KiwiSolver* s, KiwiVar* var, double strength) { const KiwiErr* kiwi_solver_add_edit_var(KiwiSolver* s, const KiwiVar* var, double strength) {
return wrap_err(s, var, [strength](auto&& s, auto&& v) { return wrap_err(s, var, [strength](auto& s, const auto& v) {
s.addEditVariable(Variable(v), strength); s.addEditVariable(v, strength);
}); });
} }
const KiwiErr* kiwi_solver_remove_edit_var(KiwiSolver* s, KiwiVar* var) { const KiwiErr* kiwi_solver_remove_edit_var(KiwiSolver* s, const KiwiVar* var) {
return wrap_err(s, var, [](auto&& s, auto&& v) { s.removeEditVariable(Variable(v)); }); return wrap_err(s, var, [](auto& s, const auto& v) { s.removeEditVariable(v); });
} }
bool kiwi_solver_has_edit_var(const KiwiSolver* s, KiwiVar* var) { bool kiwi_solver_has_edit_var(const KiwiSolver* s, const KiwiVar* var) {
if (lk_unlikely(!s || !var)) if (lk_unlikely(!s || !var))
return 0; return 0;
return s->solver.hasEditVariable(Variable(var)); return s->solver.hasEditVariable(*var);
} }
const KiwiErr* kiwi_solver_suggest_value(KiwiSolver* s, KiwiVar* var, double value) { const KiwiErr* kiwi_solver_suggest_value(KiwiSolver* s, const KiwiVar* var, double value) {
return wrap_err(s, var, [value](auto&& s, auto&& v) { s.suggestValue(Variable(v), value); }); return wrap_err(s, var, [value](auto& s, const auto& v) { s.suggestValue(v, value); });
} }
void kiwi_solver_update_vars(KiwiSolver* s) { void kiwi_solver_update_vars(KiwiSolver* s) {
@@ -374,31 +353,4 @@ char* kiwi_solver_dumps(const KiwiSolver* s) {
return buf; 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" } // extern "C"

View File

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

View File

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

150
kiwi.lua
View File

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

View File

@@ -26,70 +26,17 @@ enum RelationalOperator
OP_EQ OP_EQ
}; };
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 class Constraint
{ {
public:
explicit Constraint(ConstraintData *p) : m_data(p) {}
public:
Constraint() = default; Constraint() = default;
Constraint(const Expression &expr, Constraint(const Expression &expr,
RelationalOperator op, RelationalOperator op,
double strength = strength::required) : m_data(new ConstraintData(expr, op, strength)) {} double strength = strength::required) : m_data(new ConstraintData(expr, op, strength)) {}
Constraint(const Constraint &other, double strength) : m_data(new ConstraintData(*other.m_data, strength)) {} Constraint(const Constraint &other, double strength) : m_data(new ConstraintData(other, strength)) {}
Constraint(const Constraint &) = default; Constraint(const Constraint &) = default;
@@ -97,10 +44,32 @@ public:
~Constraint() = default; ~Constraint() = default;
const Expression &expression() const { return m_data->expression(); } const Expression &expression() const
RelationalOperator op() const { return m_data->op(); } {
double strength() const { return m_data->strength(); } return m_data->m_expression;
bool violated() const { return m_data->violated(); } }
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();
}
bool operator!() const bool operator!() const
{ {
@@ -112,9 +81,45 @@ public:
Constraint& operator=(Constraint &&) noexcept = default; Constraint& operator=(Constraint &&) noexcept = default;
private: private:
SharedDataPtr<ConstraintData> m_data; static Expression reduce(const Expression &expr)
{
std::map<Variable, double> vars;
for (const auto & term : expr.terms())
vars[term.variable()] += term.coefficient();
public: 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;
friend bool operator<(const Constraint &lhs, const Constraint &rhs) friend bool operator<(const Constraint &lhs, const Constraint &rhs)
{ {

View File

@@ -13,54 +13,63 @@
namespace kiwi 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 class Variable
{ {
public: public:
explicit Variable(VariableData *p) : m_data(p) {} class Context
VariableData *ptr() { return m_data; } {
public:
Context() = default;
virtual ~Context() {} // LCOV_EXCL_LINE
};
Variable() : m_data(new VariableData("")) {} 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(std::string name) : m_data(new VariableData(std::move(name))) {}
Variable(const char *name) : m_data(new VariableData(name)) {}
Variable(const Variable&) = default; Variable(const Variable&) = default;
Variable(Variable&&) noexcept = default; Variable(Variable&&) noexcept = default;
~Variable() = default; ~Variable() = default;
const std::string &name() const { return m_data->name(); } const std::string &name() const
void setName(const char *name) { m_data->setName(name); } {
void setName(const std::string &name) { m_data->setName(name); } return m_data->m_name;
}
double value() const { return m_data->value(); } void setName(const char *name)
void setValue(double value) { m_data->setValue(value); } {
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;
}
// operator== is used for symbolics // operator== is used for symbolics
bool equals(const Variable &other) const bool equals(const Variable &other) const
@@ -73,6 +82,32 @@ public:
Variable& operator=(Variable&&) noexcept = default; Variable& operator=(Variable&&) noexcept = default;
private: 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; SharedDataPtr<VariableData> m_data;
friend bool operator<(const Variable &lhs, const Variable &rhs) friend bool operator<(const Variable &lhs, const Variable &rhs)

37
ljkiwi.def Normal file
View File

@@ -0,0 +1,37 @@
EXPORTS
luaopen_ljkiwi
kiwi_ti_KiwiVar
kiwi_ti_KiwiConstraint
kiwi_ti_KiwiSolver
kiwi_constraint_construct
kiwi_constraint_expression
kiwi_constraint_op
kiwi_constraint_release
kiwi_constraint_retain
kiwi_constraint_strength
kiwi_constraint_violated
kiwi_expression_destroy
kiwi_solver_add_constraint
kiwi_solver_add_edit_var
kiwi_solver_construct
kiwi_solver_destroy
kiwi_solver_dump
kiwi_solver_dumps
kiwi_solver_has_constraint
kiwi_solver_has_edit_var
kiwi_solver_remove_constraint
kiwi_solver_remove_edit_var
kiwi_solver_reset
kiwi_solver_suggest_value
kiwi_solver_update_vars
kiwi_var_construct
kiwi_var_destroy
kiwi_var_eq
kiwi_var_name
kiwi_var_release
kiwi_var_retain
kiwi_var_set_name
kiwi_var_set_value
kiwi_var_value

View File

@@ -1,29 +1,33 @@
#ifndef LJKIWI_LUACOMPAT_H_ #ifndef LKIWI_LUACOMPAT_H_
#define LJKIWI_LUACOMPAT_H_ #define LKIWI_LUACOMPAT_H_
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif // __cplusplus #endif // __cplusplus
#include <lauxlib.h> #include <lauxlib.h>
#include <lua.h> #include <lua.h>
#include <lualib.h> #include <lualib.h>
#ifdef __cplusplus
#if defined(LUA_VERSION_NUM) && LUA_VERSION_NUM == 501 && defined(__GNUC__) }
#define LJKIWI_LJ_COMPAT_ATTR __attribute__((weak, visibility("default"))) #endif // __cplusplus
#else
#define LJKIWI_LJ_COMPAT_ATTR static
#endif
#if !defined(LUA_VERSION_NUM) || LUA_VERSION_NUM == 501 #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) { static int lua_absindex(lua_State* L, int i) {
if (i < 0 && i > LUA_REGISTRYINDEX) if (i < 0 && i > LUA_REGISTRYINDEX)
i += lua_gettop(L) + 1; i += lua_gettop(L) + 1;
return i; return i;
} }
LJKIWI_LJ_COMPAT_ATTR lua_Number lua_tonumberx(lua_State* L, int i, int* isnum) { static lua_Number lua_tonumberx(lua_State* L, int i, int* isnum) {
lua_Number n = lua_tonumber(L, i); lua_Number n = lua_tonumber(L, i);
if (isnum != NULL) { if (isnum != NULL) {
*isnum = (n != 0 || lua_isnumber(L, i)); *isnum = (n != 0 || lua_isnumber(L, i));
@@ -31,7 +35,7 @@ LJKIWI_LJ_COMPAT_ATTR lua_Number lua_tonumberx(lua_State* L, int i, int* isnum)
return n; return n;
} }
LJKIWI_LJ_COMPAT_ATTR lua_Integer lua_tointegerx(lua_State* L, int i, int* isnum) { static lua_Integer lua_tointegerx(lua_State* L, int i, int* isnum) {
int ok = 0; int ok = 0;
lua_Number n = lua_tonumberx(L, i, &ok); lua_Number n = lua_tonumberx(L, i, &ok);
if (ok) { if (ok) {
@@ -140,8 +144,4 @@ static int luaL_typeerror(lua_State* L, int arg, const char* tname) {
#define luaL_checkversion(L) ((void)0) #define luaL_checkversion(L) ((void)0)
#endif #endif
#ifdef __cplusplus #endif // LKIWI_LUACOMPAT_H_
}
#endif // __cplusplus
#endif // LJKIWI_LUACOMPAT_H_

View File

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

View File

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

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

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

View File

@@ -1,245 +0,0 @@
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,8 +127,6 @@ describe("Var", function()
assert.equal(v2, terms[2].var) assert.equal(v2, terms[2].var)
assert.equal(-1.0, terms[2].coefficient) assert.equal(-1.0, terms[2].coefficient)
-- TODO: terms and expressions
assert.error(function() assert.error(function()
local _ = v - "foo" local _ = v - "foo"
end) end)

32
t.lua Normal file
View File

@@ -0,0 +1,32 @@
local kiwi = require("kiwi")
local c
--debug.getupvalue
do
local v1 = kiwi.Var("v1")
local v2 = kiwi.Var("v2")
local v3 = kiwi.Var("v3")
local v4 = kiwi.Var("v4")
local v5 = kiwi.Var("v5")
local c1 = (3 * v1 + 4 * v2 + 6 * v3):eq(0)
local c2 = (6 * v4 + 4 * v5 + 2 * v1 + 1.4 * v1 / 0.3):le(1000)
local e = c1:expression()
local s = kiwi.Solver()
s:add_constraints({ c1, c2, c1 })
print(s:dumps())
end
-- c = (3 * v1 + 4 * v2 + 6 * v3):eq(0)
-- local t = c:expression():terms()
-- print(t[2].var)
-- for k, v in ipairs(t) do
-- print(k, v.var, v.coefficient)
-- end
-- for k, v in pairs(kiwi.ErrKind) do
-- print(k, v)
-- end

204
tmp/enaml.lua Normal file
View File

@@ -0,0 +1,204 @@
local kiwi = require("kiwi")
local Var = kiwi.Var
---@param solver kiwi.Solver
local function build_solver(solver)
-- create custom strength
--
local width = Var("width")
local height = Var("height")
local mmedium = kiwi.strength.create(0.0, 1.0, 0.0, 1.25)
local smedium = kiwi.strength.create(0.0, 100, 0.0)
local left = Var("left")
local top = Var("top")
local contents_top = Var("contents_top")
local contents_bottom = Var("contents_bottom")
local contents_left = Var("contents_left")
local contents_right = Var("contents_right")
local midline = Var("midline")
local ctleft = Var("ctleft")
local ctheight = Var("ctheight")
local cttop = Var("cttop")
local ctwidth = Var("ctwidth")
local lb1left = Var("lb1left")
local lb1height = Var("lb1height")
local lb1top = Var("lb1top")
local lb1width = Var("lb1width")
local lb2left = Var("lb2left")
local lb2height = Var("lb2height")
local lb2top = Var("lb2top")
local lb2width = Var("lb2width")
local lb3left = Var("lb3left")
local lb3height = Var("lb3height")
local lb3top = Var("lb3top")
local lb3width = Var("lb3width")
local fl1left = Var("fl1left")
local fl1height = Var("fl1height")
local fl1top = Var("fl1top")
local fl1width = Var("fl1width")
local fl2left = Var("fl2left")
local fl2height = Var("fl2height")
local fl2top = Var("fl2top")
local fl2width = Var("fl2width")
local fl3left = Var("fl3left")
local fl3height = Var("fl3height")
local fl3top = Var("fl3top")
local fl3width = Var("fl3width")
solver:add_edit_var(width, kiwi.strength.STRONG)
solver:add_edit_var(height, kiwi.strength.STRONG)
local strong, medium, weak = kiwi.strength.STRONG, kiwi.strength.MEDIUM, kiwi.strength.WEAK
local constraints = {
(left + -0):ge(0),
(height + 0):eq(0, medium),
(top + -0):ge(0),
(width + -0):ge(0),
(height + -0):ge(0),
(-top + contents_top + -10):eq(0),
(lb3height + -16):eq(0, strong),
(lb3height + -16):ge(0, strong),
(ctleft + -0):ge(0),
(cttop + -0):ge(0),
(ctwidth + -0):ge(0),
(ctheight + -0):ge(0),
(fl3left + -0):ge(0),
(ctheight + -24):ge(0, smedium),
(ctwidth + -1.67772e+07):le(0, smedium),
(ctheight + -24):le(0, smedium),
(fl3top + -0):ge(0),
(fl3width + -0):ge(0),
(fl3height + -0):ge(0),
(lb1width + -67):eq(0, weak),
(lb2width + -0):ge(0),
(lb2height + -0):ge(0),
(fl2height + -0):ge(0),
(lb3left + -0):ge(0),
(fl2width + -125):ge(0, strong),
(fl2height + -21):eq(0, strong),
(fl2height + -21):ge(0, strong),
(lb3top + -0):ge(0),
(lb3width + -0):ge(0),
(fl1left + -0):ge(0),
(fl1width + -0):ge(0),
(lb1width + -67):ge(0, strong),
(fl2left + -0):ge(0),
(lb2width + -66):eq(0, weak),
(lb2width + -66):ge(0, strong),
(lb2height + -16):eq(0, strong),
(fl1height + -0):ge(0),
(fl1top + -0):ge(0),
(lb2top + -0):ge(0),
(-lb2top + lb3top + -lb2height + -10):eq(0, mmedium),
(-lb3top + -lb3height + fl3top + -10):ge(0),
(-lb3top + -lb3height + fl3top + -10):eq(0, mmedium),
(contents_bottom + -fl3height + -fl3top + -0):eq(0, mmedium),
(fl1top + -contents_top + 0):ge(0),
(fl1top + -contents_top + 0):eq(0, mmedium),
(contents_bottom + -fl3height + -fl3top + -0):ge(0),
(-left + -width + contents_right + 10):eq(0),
(-top + -height + contents_bottom + 10):eq(0),
(-left + contents_left + -10):eq(0),
(lb3left + -contents_left + 0):eq(0, mmedium),
(fl1left + -midline + 0):eq(0, strong),
(fl2left + -midline + 0):eq(0, strong),
(ctleft + -midline + 0):eq(0, strong),
(fl1top + 0.5 * fl1height + -lb1top + -0.5 * lb1height + 0):eq(0, strong),
(lb1left + -contents_left + 0):ge(0),
(lb1left + -contents_left + 0):eq(0, mmedium),
(-lb1left + fl1left + -lb1width + -10):ge(0),
(-lb1left + fl1left + -lb1width + -10):eq(0, mmedium),
(-fl1left + contents_right + -fl1width + -0):ge(0),
(width + 0):eq(0, medium),
(-fl1top + fl2top + -fl1height + -10):ge(0),
(-fl1top + fl2top + -fl1height + -10):eq(0, mmedium),
(cttop + -fl2top + -fl2height + -10):ge(0),
(-ctheight + -cttop + fl3top + -10):ge(0),
(contents_bottom + -fl3height + -fl3top + -0):ge(0),
(cttop + -fl2top + -fl2height + -10):eq(0, mmedium),
(-fl1left + contents_right + -fl1width + -0):eq(0, mmedium),
(-lb2top + -0.5 * lb2height + fl2top + 0.5 * fl2height + 0):eq(0, strong),
(-contents_left + lb2left + 0):ge(0),
(-contents_left + lb2left + 0):eq(0, mmedium),
(fl2left + -lb2width + -lb2left + -10):ge(0),
(-ctheight + -cttop + fl3top + -10):eq(0, mmedium),
(contents_bottom + -fl3height + -fl3top + -0):eq(0, mmedium),
(lb1top + -0):ge(0),
(lb1width + -0):ge(0),
(lb1height + -0):ge(0),
(fl2left + -lb2width + -lb2left + -10):eq(0, mmedium),
(-fl2left + -fl2width + contents_right + -0):eq(0, mmedium),
(-fl2left + -fl2width + contents_right + -0):ge(0),
(lb3left + -contents_left + 0):ge(0),
(lb1left + -0):ge(0),
(0.5 * ctheight + cttop + -lb3top + -0.5 * lb3height + 0):eq(0, strong),
(ctleft + -lb3left + -lb3width + -10):ge(0),
(-ctwidth + -ctleft + contents_right + -0):ge(0),
(ctleft + -lb3left + -lb3width + -10):eq(0, mmedium),
(fl3left + -contents_left + 0):ge(0),
(fl3left + -contents_left + 0):eq(0, mmedium),
(-ctwidth + -ctleft + contents_right + -0):eq(0, mmedium),
(-fl3left + contents_right + -fl3width + -0):eq(0, mmedium),
(-contents_top + lb1top + 0):ge(0),
(-contents_top + lb1top + 0):eq(0, mmedium),
(-fl3left + contents_right + -fl3width + -0):ge(0),
(lb2top + -lb1top + -lb1height + -10):ge(0),
(-lb2top + lb3top + -lb2height + -10):ge(0),
(lb2top + -lb1top + -lb1height + -10):eq(0, mmedium),
(fl1height + -21):eq(0, strong),
(fl1height + -21):ge(0, strong),
(lb2left + -0):ge(0),
(lb2height + -16):ge(0, strong),
(fl2top + -0):ge(0),
(fl2width + -0):ge(0),
(lb1height + -16):ge(0, strong),
(lb1height + -16):eq(0, strong),
(fl3width + -125):ge(0, strong),
(fl3height + -21):eq(0, strong),
(fl3height + -21):ge(0, strong),
(lb3height + -0):ge(0),
(ctwidth + -119):ge(0, smedium),
(lb3width + -24):eq(0, weak),
(lb3width + -24):ge(0, strong),
(fl1width + -125):ge(0, strong),
}
for _, c in ipairs(constraints) do
print(c)
solver:add_constraint(c)
end
return width, height
end
local function main()
local sizes = {
{ w = 400, h = 600 },
{ w = 600, h = 400 },
{ w = 800, h = 1200 },
{ w = 1200, h = 800 },
{ w = 400, h = 800 },
{ w = 800, h = 400 },
}
for i = 1, 1 do
local solver = kiwi.Solver()
local width, height = build_solver(solver)
for _, size in ipairs(sizes) do
solver:suggest_value(width, size.w)
solver:suggest_value(height, size.h)
solver:update_vars()
print(width:value(), height:value())
end
end
end
main()
collectgarbage("collect")
collectgarbage("collect")
collectgarbage("collect")
collectgarbage("collect")

90
tmp/ex.lua Normal file
View File

@@ -0,0 +1,90 @@
local kiwi = require("kiwi")
local Var = kiwi.Var
local Button = setmetatable({}, {
__call = function(_, identifier)
return setmetatable({
left = Var(identifier .. " left"),
width = Var(identifier .. " width"),
}, {
__tostring = function(self)
return "Button(" .. self.left:value() .. ", " .. self.width:value() .. ")"
end,
})
end,
})
local b1 = Button("b1")
local b2 = Button("b2")
local left_edge = Var("left")
local right_edge = Var("width")
local STRONG = kiwi.Strength.STRONG
local constraints
for i = 1, 200 do
q = kiwi.Term(left_edge, 2.0)
end
for i = 1, 50, 1 do
-- stylua: ignore start
constraints = {
left_edge :eq(0.0),
-- two buttons are the same width
b1.width :eq(b2.width),
-- button1 starts 50 for the left margin
(1.00 * b1.left) :eq(left_edge + 50),
-- button2 ends 50 from the right margin
right_edge :eq(b2.left + b2.width + 50),
-- button2 starts at least 100 from the end of button1. This is the "elastic" constraint
b2.left :ge(b1.left + b1.width + 100),
-- button1 has a minimum width of 87
b1.width :ge(87),
-- button1 has a preferred width of 87
b1.width :eq(87, STRONG),
-- button2 has minimum width of 113
b2.width :ge(113),
-- button2 has a preferred width of 113
b2.width :eq(113, STRONG),
}
-- stylua: ignore end
--
k = kiwi.Strength.create(0.0, 1.0, 0.0, 1.25)
end
local solver = kiwi.Solver()
for _, c in ipairs(constraints) do
print(c)
solver:add_constraint(c)
end
solver:update_vars()
print(b1) -- Button(50, 113)
print(b2) -- Button(263, 113)
print(left_edge:value()) -- 0
print(right_edge:value()) -- 426
solver:add_edit_var(right_edge, STRONG)
solver:suggest_value(right_edge, 500)
solver:update_vars()
-- solver:dump()
-- print(b1) -- Button(50, 113)
-- print(b2) -- Button(337, 113)
-- print(right_edge:value()) -- 500
-- print(solver:dumps())
-- solver = kiwi.Solver()
-- local trailing = Var("trailing")
-- local leading = Var("leading")
-- solver:add_constraint(kiwi.new_single_constraint(trailing, 86))
-- solver:add_constraint(kiwi.new_pair_constraint(trailing, leading, 8.0))
-- print(solver:dumps())
-- solver:update_vars()
-- print(trailing:value()) -- 86
-- print(leading:value()) -- 76

28
tmp/ex2.lua Normal file
View File

@@ -0,0 +1,28 @@
local kiwi = require("kiwi")
local Var = kiwi.Var
local function doit(v, i)
v:set(i)
end
local v = Var("fuckit")
local s = kiwi.Solver()
local e
for i = 1, 1000 do
e = kiwi.Expression(5.0, kiwi.Term(v, 3.0), kiwi.Term(v, 2.0))
end
--local e = (v + 1)
s:add_constraint(e:eq(100))
print(v:value())
s:update_vars()
v:set_name("fuck")
print(v:value())
s:dump()

49
tmp/ex3.lua Normal file
View File

@@ -0,0 +1,49 @@
local kiwi = require("kiwi")
local ffi = require("ffi")
local t
local e
local strformat = string.format
local Strength = kiwi.Strength
--local bad_str = "-1 v8 + -1 v4 + -1 v2 + -4 v9 + -2 v3 + -1 v6 + -1 v5 + -1 v1 + -2 v7 + -5"
-- local b = ""
function s(self)
local ops = {
[0] = "<=",
">=",
"==",
}
local strengths = {
[Strength.REQUIRED] = "required",
[Strength.STRONG] = "strong",
[Strength.MEDIUM] = "medium",
[Strength.WEAK] = "weak",
}
local strength = self:strength()
--local e = self:expression()
local s = strformat("%s %s %s", "aaa", tostring(self:expression()), tonumber(self:op()))
return s
end
for i = 1, 2000 do
local v1 = kiwi.Var("v1")
local v2 = kiwi.Var("v2")
local v3 = kiwi.Var("v3")
local v4 = kiwi.Var("v4")
local v5 = kiwi.Var("v5")
local v6 = kiwi.Var("v6")
local v7 = kiwi.Var("v7")
local v8 = kiwi.Var("v8")
local v9 = kiwi.Var("v9")
local v10 = kiwi.Var("v9")
do
--
local c = ((-(v1 + v2 + 2 * v3 + v4 + 3)):eq(v5 + v6 + 2 * v7 + v8 + 4 * v9 + 3))
s(c)
end
end
print(t)

37
tmp/ex4.lua Normal file
View File

@@ -0,0 +1,37 @@
local kiwi = require("kiwi")
local ffi = require("ffi")
local Var = kiwi.Var
local v1 = Var("v1")
local v2 = Var("v2")
local v3 = Var("v3")
local v4 = Var("v4")
local v5 = Var("v5")
local v6 = Var("v6")
local f1 = kiwi.f1
local f2 = kiwi.f2
local single = kiwi.constraints.single
local function execute_times(f, times)
local begin = os.clock()
for _ = 1, times do
f()
end
local finish = os.clock()
return finish - begin
end
local t = execute_times(function()
return kiwi.constraints.pair_ratio(v1, 2.0, v2, 3.0)
end, 6000000)
print(t)
-- local t = execute_times(function()
-- return pair_ratio2(v1, 2.0, v2, 3.0)
-- end, 2000000)
-- print(t)

41
tmp/ex5.lua Normal file
View File

@@ -0,0 +1,41 @@
local kiwi = require("kiwi")
local Var = kiwi.Var
x1 = Var("x1")
x2 = Var("x2")
xm = Var("xm")
local constraints = { x1:ge(0), x2:le(100), x2:ge(x1 + 10), xm:eq((x1 + x2) / 2) }
local solver = kiwi.Solver()
for _, c in ipairs(constraints) do
solver:add_constraint(c)
end
local c = kiwi.constraints.single(x1, 40, "EQ", kiwi.Strength.WEAK)
solver:add_constraint(c)
constraints[#constraints + 1] = c
solver:add_edit_var(xm, kiwi.Strength.STRONG)
solver:suggest_value(xm, 60)
solver:update_vars()
print(xm:value(), x1:value(), x2:value())
for _, c in ipairs(constraints) do
print(c, c:violated())
end
solver:suggest_value(xm, 90)
solver:update_vars()
print(xm:value(), x1:value(), x2:value())
for _, c in ipairs(constraints) do
print(c, c:violated())
end

26
tmp/t.lua Normal file
View File

@@ -0,0 +1,26 @@
local k = require("kiwi")
s = k.Solver()
--s:set_error_mask({ "KiwiErrUnknownEditVariable" }, false)
local v1 = k.Var("v1")
local v2 = k.Var("v2")
local v3 = k.Var("v3")
local v4 = k.Var("v4")
print((2 * v1 - 5 * v2))
os.exit()
local c1 = k.constraints.pair_ratio(v1, -4, v2, 22, "GE", 1000)
local c2 = k.constraints.pair_ratio(v3, -6, v4, 29, "LE", 1000)
local vo = c1:expression():terms()[1].var
print(v1 == vo)
-- print(v1:name())
-- print(v1 * 5)
local e = k.Expression(6, k.Term(v1, 2), k.Term(v2, 3))
--print(e)
--print(k.strength.MEDIUM)

66
tmp/test.lua Normal file
View File

@@ -0,0 +1,66 @@
local kiwi = require("kiwi")
local Var = kiwi.Var
local Strength = kiwi.Strength
local Button_mt = {}
---@class Button
---@field left kiwi.Var
---@field width kiwi.Var
---@overload fun(identifier: string): Button
local Button = setmetatable({}, Button_mt)
function Button_mt.__call(_, identifier)
return setmetatable({
left = Var("left" .. identifier),
width = Var("width" .. identifier),
}, {
__index = Button,
__tostring = function(self)
return "Button(" .. self.left:value() .. ", " .. self.width:value() .. ")"
end,
})
end
local b1 = Button("b1")
local b2 = Button("b2")
local left_limit = kiwi.Var("left")
local right_limit = kiwi.Var("width")
-- stylua: ignore start
local constraints = {
left_limit :eq( 0 ),
b1.width :eq( b2.width ),
b1.left :eq( left_limit + 50),
right_limit:eq( b2.left + b2.width + 50),
b2.left :ge( b1.left + b1.width + 100),
b1.width :ge( 87 ),
b1.width :eq( 87, Strength.STRONG),
b2.width :ge( 113 ),
b2.width :eq( 113, Strength.STRONG),
}
-- stylua: ignore end
local s = kiwi.Solver()
for _, c in ipairs(constraints) do
s:add_constraint(c)
end
print(constraints[4]:expression())
print(constraints[5]:expression())
kiwi.Constraint(b1.left + 1, "EQ")
s:update_vars()
print(b1)
print(b2)
print(left_limit:value())
print(right_limit:value())
print(s:dumps())