From f782003d1ee271b31f8bb32876ec643b994b29a7 Mon Sep 17 00:00:00 2001 From: cloudfreexiao <996442717qqcom@gmail.com> Date: Sun, 22 Aug 2021 19:49:48 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=B3chore(=E5=B7=A5=E5=85=B7):=20?= =?UTF-8?q?=E8=B0=83=E6=95=B4docker=20=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker/README.md | 8 +- docker/cicd/.drone-example.yml | 15 - docker/cicd/README.md | 95 --- docker/cicd/boot.sh | 19 - docker/cicd/docker-compose.yml | 83 --- docs/README.md | 17 +- framework/lualib/3rd/misc/bump.lua | 824 +++++++++++++++++++++++++++ framework/lualib/3rd/misc/corope.lua | 326 +++++++++++ framework/lualib/3rd/misc/mm.lua | 725 +++++++++++++++++++++++ framework/lualib/3rd/misc/shash.lua | 177 ++++++ framework/lualib/3rd/misc/treap.lua | 138 +++++ 11 files changed, 2204 insertions(+), 223 deletions(-) delete mode 100755 docker/cicd/.drone-example.yml delete mode 100755 docker/cicd/README.md delete mode 100755 docker/cicd/boot.sh delete mode 100755 docker/cicd/docker-compose.yml create mode 100644 framework/lualib/3rd/misc/bump.lua create mode 100644 framework/lualib/3rd/misc/corope.lua create mode 100644 framework/lualib/3rd/misc/mm.lua create mode 100644 framework/lualib/3rd/misc/shash.lua create mode 100644 framework/lualib/3rd/misc/treap.lua diff --git a/docker/README.md b/docker/README.md index 5b22d35..4694f1b 100644 --- a/docker/README.md +++ b/docker/README.md @@ -6,4 +6,10 @@ docker rm $(docker ps -a -q) https://www.apolloconfig.com/#/zh/deployment/quick-start redis 集群 -https://github.com/Grokzen/docker-redis-cluster \ No newline at end of file +https://github.com/Grokzen/docker-redis-cluster + +本地 dns 系统: +https://github.com/mafintosh/dns-discovery + +基于 gitea + drone + docker 的 CI 流程实践 +https://zhuanlan.zhihu.com/p/266072740 \ No newline at end of file diff --git a/docker/cicd/.drone-example.yml b/docker/cicd/.drone-example.yml deleted file mode 100755 index 9265a13..0000000 --- a/docker/cicd/.drone-example.yml +++ /dev/null @@ -1,15 +0,0 @@ -kind: pipeline -type: docker -name: hello-world - -trigger: - branch: - - master - event: - - push - -steps: - - name: say-hello - image: busybox - commands: - - echo hello-world diff --git a/docker/cicd/README.md b/docker/cicd/README.md deleted file mode 100755 index 3b1826f..0000000 --- a/docker/cicd/README.md +++ /dev/null @@ -1,95 +0,0 @@ -# drone-gitea-on-docker -DroneCI and Gitea on Docker - -## Usage - -Set the following in your `boot.sh`: - -``` -IP_ADDRESS=192.168.0.6 -> either reachable dns or ip address which will be your clone address and ui addresses. -GITEA_ADMIN_USER="giteauser" -> will be the user you register with in drone -``` - -Now boot the stack: - -``` -$ bash boot.sh -``` - -*Note*: Theres a [current issue](https://github.com/go-gitea/gitea/issues/7702) where webhooks get fired twice, if you see that just restart gitea with `docker restart gitea`. - -- Head over to: `http://${IP_ADDRESS}:3000/user/settings/applications` and create a new OAuth2 Application and set the Redirect URI to `http://${IP_ADDRESS}:3001/login` - -- Capture the client id and client secret and populate them in the `boot.sh` in `DRONE_GITEA_CLIENT_ID` and `DRONE_GITEA_CLIENT_SECRET` and run `bash boot.sh` again. This will give drone the correct credentials in order to authenticate with gitea. - -- Now when you head over to `http://${IP_ADDRESS}:3001/` you will be asked to authorize the application and you should be able to access drone. - -## Drone CLI - -Install Drone CLI: -- https://docs.drone.io/cli/install/ - -``` -$ curl -L https://github.com/drone/drone-cli/releases/latest/download/drone_darwin_amd64.tar.gz | tar zx -$ sudo mv drone /usr/local/bin/drone -$ chmod +x /usr/local/bin/drone -``` - -Get your Drone Token: -- http://${IP_ADDRESS}:3001/account - -``` -$ export DRONE_SERVER=http://${IP_ADDRESS}:3001 -$ export DRONE_TOKEN=one-from-the-account-page -drone info -``` - -## Build your first pipeline - -Create a test repo in gitea: - -![image](https://user-images.githubusercontent.com/567298/110296470-0ad23800-7ffb-11eb-8428-af49d0ebd62d.png) - -Commit a `.drone.yml` file for drone: - -``` -$ cat .drone.yml -kind: pipeline -type: docker -name: hello-world - -trigger: - branch: - - master - event: - - push - -steps: - - name: say-hello - image: busybox - commands: - - echo hello-world -``` - -Head over to drone and sync your repositories: - -![image](https://user-images.githubusercontent.com/567298/110296425-00b03980-7ffb-11eb-9216-76725a62c09e.png) - -Activate your repository: - -![image](https://user-images.githubusercontent.com/567298/110296623-3523f580-7ffb-11eb-805f-db5db4dab0cb.png) - -Push a commit to master and see your pipeline running: - -![image](https://user-images.githubusercontent.com/567298/110296747-584ea500-7ffb-11eb-9909-259641a663aa.png) - -## More Examples - -- https://github.com/ruanbekker/drone-ci-testing -- https://github.com/ruanbekker/drone-demo-python-flask -- https://github.com/ruanbekker/drone-with-go -- https://github.com/ruanbekker/demo-drone-mongodb-tests -- https://github.com/ruanbekker/drone-multi-pipeline -- https://github.com/ruanbekker/docker-jekyll-drone -- [Localstack with Drone and Gitea](https://gist.github.com/ruanbekker/84cb9f0c2a21434ca8381a0c74842d84) -- [Drone, Minio, Gitea, Sqlite on Docker Compose](https://gist.github.com/ruanbekker/3847bbf1b961efc568b93ccbf5c6f9f6) diff --git a/docker/cicd/boot.sh b/docker/cicd/boot.sh deleted file mode 100755 index c0f5e4b..0000000 --- a/docker/cicd/boot.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env bash - -export HOSTNAME=$(hostname) -export DRONE_VERSION=1.10.1 -export DRONE_RUNNER_VERSION=1.6.3 -export GITEA_VERSION=1.13 -export IP_ADDRESS=127.0.0.1 -export MINIO_ACCESS_KEY="EXAMPLEKEY" -export MINIO_SECRET_KEY="EXAMPLESECRET" -export GITEA_ADMIN_USER="example" -export DRONE_RPC_SECRET="$(echo ${HOSTNAME} | openssl dgst -md5 -hex)" -export DRONE_USER_CREATE="username:${GITEA_ADMIN_USER},machine:false,admin:true,token:${DRONE_RPC_SECRET}" -export DRONE_GITEA_CLIENT_ID="" -export DRONE_GITEA_CLIENT_SECRET="" -docker-compose up -d - -echo "" -echo "Gitea: http://${IP_ADDRESS}:3000/" -echo "Drone: http://${IP_ADDRESS}:3001/" diff --git a/docker/cicd/docker-compose.yml b/docker/cicd/docker-compose.yml deleted file mode 100755 index 122d9bc..0000000 --- a/docker/cicd/docker-compose.yml +++ /dev/null @@ -1,83 +0,0 @@ -version: '3.6' - -services: - gitea: - container_name: gitea - image: gitea/gitea:${GITEA_VERSION:-1.10.6} - restart: unless-stopped - environment: - # https://docs.gitea.io/en-us/install-with-docker/#environments-variables - - APP_NAME="Gitea" - - USER_UID=1000 - - USER_GID=1000 - - RUN_MODE=prod - - DOMAIN=${IP_ADDRESS} - - SSH_DOMAIN=${IP_ADDRESS} - - HTTP_PORT=3000 - - ROOT_URL=http://${IP_ADDRESS}:3000 - - SSH_PORT=222 - - SSH_LISTEN_PORT=22 - - DB_TYPE=sqlite3 - ports: - - "3000:3000" - - "222:22" - networks: - - cicd_net - volumes: - - ./gitea:/data - - drone: - container_name: drone - image: drone/drone:${DRONE_VERSION:-1.6.4} - restart: unless-stopped - depends_on: - - gitea - environment: - # https://docs.drone.io/server/provider/gitea/ - - DRONE_DATABASE_DRIVER=sqlite3 - - DRONE_DATABASE_DATASOURCE=/data/database.sqlite - - DRONE_GITEA_SERVER=http://${IP_ADDRESS}:3000/ - - DRONE_GIT_ALWAYS_AUTH=false - - DRONE_RPC_SECRET=${DRONE_RPC_SECRET} - - DRONE_SERVER_PROTO=http - - DRONE_SERVER_HOST=${IP_ADDRESS}:3001 - - DRONE_TLS_AUTOCERT=false - - DRONE_USER_CREATE=${DRONE_USER_CREATE} - - DRONE_GITEA_CLIENT_ID=${DRONE_GITEA_CLIENT_ID} - - DRONE_GITEA_CLIENT_SECRET=${DRONE_GITEA_CLIENT_SECRET} - ports: - - "3001:80" - - "9001:9000" - networks: - - cicd_net - volumes: - - /var/run/docker.sock:/var/run/docker.sock - - ./drone:/data - - drone-runner: - container_name: drone-runner - image: drone/drone-runner-docker:${DRONE_RUNNER_VERSION:-1} - restart: unless-stopped - depends_on: - - drone - environment: - # https://docs.drone.io/runner/docker/installation/linux/ - # https://docs.drone.io/server/metrics/ - - DRONE_RPC_PROTO=http - - DRONE_RPC_HOST=drone - - DRONE_RPC_SECRET=${DRONE_RPC_SECRET} - - DRONE_RUNNER_NAME="${HOSTNAME}-runner" - - DRONE_RUNNER_CAPACITY=2 - - DRONE_RUNNER_NETWORKS=cicd_net - - DRONE_DEBUG=false - - DRONE_TRACE=false - ports: - - "3002:3000" - networks: - - cicd_net - volumes: - - /var/run/docker.sock:/var/run/docker.sock - -networks: - cicd_net: - name: cicd_net diff --git a/docs/README.md b/docs/README.md index b46373d..6a89740 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,16 +1,6 @@ -删除所有 容器和镜像 - -docker stop $(docker ps -a -q) && docker system prune --all --force - -配置中心(apollo) -ELK - 代码安全指南: https://github.com/Tencent/secguide -本地 dns 系统: -https://github.com/mafintosh/dns-discovery - 100个gdb小技巧 https://github.com/hellogcc/100-gdb-tips @@ -20,6 +10,13 @@ https://github.com/ibraheemdev/modern-unix A tool for writing better scripts https://github.com/google/zx +Nexe is a command-line utility that compiles your Node.js application into a single executable file +https://github.com/nexe/nexe + +ShellJS - Unix shell commands for Node.js +https://github.com/shelljs/shelljs + + https://github.com/cloudwu/skynet/issues/288 diff --git a/framework/lualib/3rd/misc/bump.lua b/framework/lualib/3rd/misc/bump.lua new file mode 100644 index 0000000..95e74dd --- /dev/null +++ b/framework/lualib/3rd/misc/bump.lua @@ -0,0 +1,824 @@ +local bump = { + _VERSION = 'bump v3.1.7', + _URL = 'https://github.com/kikito/bump.lua', + _DESCRIPTION = 'A collision detection library for Lua', +} + +------------------------------------------ +-- Auxiliary functions +------------------------------------------ +local DELTA = 1e-10 -- floating-point margin of error + +local abs, floor, ceil, min, max = math.abs, math.floor, math.ceil, math.min, math.max + +local function sign(x) + if x > 0 then + return 1 + end + if x == 0 then + return 0 + end + return -1 +end + +local function nearest(x, a, b) + if abs(a - x) < abs(b - x) then + return a + else + return b + end +end + +local function assertType(desiredType, value, name) + if type(value) ~= desiredType then + error(name .. ' must be a ' .. desiredType .. ', but was ' .. tostring(value) .. '(a ' .. type(value) .. ')') + end +end + +local function assertIsPositiveNumber(value, name) + if type(value) ~= 'number' or value <= 0 then + error(name .. ' must be a positive integer, but was ' .. tostring(value) .. '(' .. type(value) .. ')') + end +end + +local function assertIsRect(x, y, w, h) + assertType('number', x, 'x') + assertType('number', y, 'y') + assertIsPositiveNumber(w, 'w') + assertIsPositiveNumber(h, 'h') +end + +local defaultFilter = function() + return 'slide' +end + +------------------------------------------ +-- Rectangle functions +------------------------------------------ + +local function rect_getNearestCorner(x, y, w, h, px, py) + return nearest(px, x, x + w), nearest(py, y, y + h) +end + +-- This is a generalized implementation of the liang-barsky algorithm, which also returns +-- the normals of the sides where the segment intersects. +-- Returns nil if the segment never touches the rect +-- Notice that normals are only guaranteed to be accurate when initially ti1, ti2 == -math.huge, math.huge +local function rect_getSegmentIntersectionIndices(x, y, w, h, x1, y1, x2, y2, ti1, ti2) + ti1, ti2 = ti1 or 0, ti2 or 1 + local dx, dy = x2 - x1, y2 - y1 + local nx, ny + local nx1, ny1, nx2, ny2 = 0, 0, 0, 0 + local p, q, r + + for side = 1, 4 do + if side == 1 then + nx, ny, p, q = -1, 0, -dx, x1 - x -- left + elseif side == 2 then + nx, ny, p, q = 1, 0, dx, x + w - x1 -- right + elseif side == 3 then + nx, ny, p, q = 0, -1, -dy, y1 - y -- top + else + nx, ny, p, q = 0, 1, dy, y + h - y1 -- bottom + end + + if p == 0 then + if q <= 0 then + return nil + end + else + r = q / p + if p < 0 then + if r > ti2 then + return nil + elseif r > ti1 then + ti1, nx1, ny1 = r, nx, ny + end + else -- p > 0 + if r < ti1 then + return nil + elseif r < ti2 then + ti2, nx2, ny2 = r, nx, ny + end + end + end + end + + return ti1, ti2, nx1, ny1, nx2, ny2 +end + +-- Calculates the minkowsky difference between 2 rects, which is another rect +local function rect_getDiff(x1, y1, w1, h1, x2, y2, w2, h2) + return x2 - x1 - w1, y2 - y1 - h1, w1 + w2, h1 + h2 +end + +local function rect_containsPoint(x, y, w, h, px, py) + return px - x > DELTA and py - y > DELTA and x + w - px > DELTA and y + h - py > DELTA +end + +local function rect_isIntersecting(x1, y1, w1, h1, x2, y2, w2, h2) + return x1 < x2 + w2 and x2 < x1 + w1 and y1 < y2 + h2 and y2 < y1 + h1 +end + +local function rect_getSquareDistance(x1, y1, w1, h1, x2, y2, w2, h2) + local dx = x1 - x2 + (w1 - w2) / 2 + local dy = y1 - y2 + (h1 - h2) / 2 + return dx * dx + dy * dy +end + +local function rect_detectCollision(x1, y1, w1, h1, x2, y2, w2, h2, goalX, goalY) + goalX = goalX or x1 + goalY = goalY or y1 + + local dx, dy = goalX - x1, goalY - y1 + local x, y, w, h = rect_getDiff(x1, y1, w1, h1, x2, y2, w2, h2) + + local overlaps, ti, nx, ny + + if rect_containsPoint(x, y, w, h, 0, 0) then -- item was intersecting other + local px, py = rect_getNearestCorner(x, y, w, h, 0, 0) + local wi, hi = min(w1, abs(px)), min(h1, abs(py)) -- area of intersection + ti = -wi * hi -- ti is the negative area of intersection + overlaps = true + else + local ti1, ti2, nx1, ny1 = rect_getSegmentIntersectionIndices(x, y, w, h, 0, 0, dx, dy, -math.huge, math.huge) + + -- item tunnels into other + if ti1 and ti1 < 1 and (abs(ti1 - ti2) >= DELTA) -- special case for rect going through another rect's corner + and (0 < ti1 + DELTA or 0 == ti1 and ti2 > 0) then + ti, nx, ny = ti1, nx1, ny1 + overlaps = false + end + end + + if not ti then + return + end + + local tx, ty + + if overlaps then + if dx == 0 and dy == 0 then + -- intersecting and not moving - use minimum displacement vector + local px, py = rect_getNearestCorner(x, y, w, h, 0, 0) + if abs(px) < abs(py) then + py = 0 + else + px = 0 + end + nx, ny = sign(px), sign(py) + tx, ty = x1 + px, y1 + py + else + -- intersecting and moving - move in the opposite direction + local ti1, _ + ti1, _, nx, ny = rect_getSegmentIntersectionIndices(x, y, w, h, 0, 0, dx, dy, -math.huge, 1) + if not ti1 then + return + end + tx, ty = x1 + dx * ti1, y1 + dy * ti1 + end + else -- tunnel + tx, ty = x1 + dx * ti, y1 + dy * ti + end + + return { + overlaps = overlaps, + ti = ti, + move = { + x = dx, + y = dy, + }, + normal = { + x = nx, + y = ny, + }, + touch = { + x = tx, + y = ty, + }, + itemRect = { + x = x1, + y = y1, + w = w1, + h = h1, + }, + otherRect = { + x = x2, + y = y2, + w = w2, + h = h2, + }, + } +end + +------------------------------------------ +-- Grid functions +------------------------------------------ + +local function grid_toWorld(cellSize, cx, cy) + return (cx - 1) * cellSize, (cy - 1) * cellSize +end + +local function grid_toCell(cellSize, x, y) + return floor(x / cellSize) + 1, floor(y / cellSize) + 1 +end + +-- grid_traverse* functions are based on "A Fast Voxel Traversal Algorithm for Ray Tracing", +-- by John Amanides and Andrew Woo - http://www.cse.yorku.ca/~amana/research/grid.pdf +-- It has been modified to include both cells when the ray "touches a grid corner", +-- and with a different exit condition + +local function grid_traverse_initStep(cellSize, ct, t1, t2) + local v = t2 - t1 + if v > 0 then + return 1, cellSize / v, ((ct + v) * cellSize - t1) / v + elseif v < 0 then + return -1, -cellSize / v, ((ct + v - 1) * cellSize - t1) / v + else + return 0, math.huge, math.huge + end +end + +local function grid_traverse(cellSize, x1, y1, x2, y2, f) + local cx1, cy1 = grid_toCell(cellSize, x1, y1) + local cx2, cy2 = grid_toCell(cellSize, x2, y2) + local stepX, dx, tx = grid_traverse_initStep(cellSize, cx1, x1, x2) + local stepY, dy, ty = grid_traverse_initStep(cellSize, cy1, y1, y2) + local cx, cy = cx1, cy1 + + f(cx, cy) + + -- The default implementation had an infinite loop problem when + -- approaching the last cell in some occassions. We finish iterating + -- when we are *next* to the last cell + while abs(cx - cx2) + abs(cy - cy2) > 1 do + if tx < ty then + tx, cx = tx + dx, cx + stepX + f(cx, cy) + else + -- Addition: include both cells when going through corners + if tx == ty then + f(cx + stepX, cy) + end + ty, cy = ty + dy, cy + stepY + f(cx, cy) + end + end + + -- If we have not arrived to the last cell, use it + if cx ~= cx2 or cy ~= cy2 then + f(cx2, cy2) + end + +end + +local function grid_toCellRect(cellSize, x, y, w, h) + local cx, cy = grid_toCell(cellSize, x, y) + local cr, cb = ceil((x + w) / cellSize), ceil((y + h) / cellSize) + return cx, cy, cr - cx + 1, cb - cy + 1 +end + +------------------------------------------ +-- Responses +------------------------------------------ + +local touch = function(world, col, x, y, w, h, goalX, goalY, filter) + return col.touch.x, col.touch.y, {}, 0 +end + +local cross = function(world, col, x, y, w, h, goalX, goalY, filter) + local cols, len = world:project(col.item, x, y, w, h, goalX, goalY, filter) + return goalX, goalY, cols, len +end + +local slide = function(world, col, x, y, w, h, goalX, goalY, filter) + goalX = goalX or x + goalY = goalY or y + + local tch, move = col.touch, col.move + if move.x ~= 0 or move.y ~= 0 then + if col.normal.x ~= 0 then + goalX = tch.x + else + goalY = tch.y + end + end + + col.slide = { + x = goalX, + y = goalY, + } + + x, y = tch.x, tch.y + local cols, len = world:project(col.item, x, y, w, h, goalX, goalY, filter) + return goalX, goalY, cols, len +end + +local bounce = function(world, col, x, y, w, h, goalX, goalY, filter) + goalX = goalX or x + goalY = goalY or y + + local tch, move = col.touch, col.move + local tx, ty = tch.x, tch.y + + local bx, by = tx, ty + + if move.x ~= 0 or move.y ~= 0 then + local bnx, bny = goalX - tx, goalY - ty + if col.normal.x == 0 then + bny = -bny + else + bnx = -bnx + end + bx, by = tx + bnx, ty + bny + end + + col.bounce = { + x = bx, + y = by, + } + x, y = tch.x, tch.y + goalX, goalY = bx, by + + local cols, len = world:project(col.item, x, y, w, h, goalX, goalY, filter) + return goalX, goalY, cols, len +end + +------------------------------------------ +-- World +------------------------------------------ + +local World = {} +local World_mt = { + __index = World, +} + +-- Private functions and methods + +local function sortByWeight(a, b) + return a.weight < b.weight +end + +local function sortByTiAndDistance(a, b) + if a.ti == b.ti then + local ir, ar, br = a.itemRect, a.otherRect, b.otherRect + local ad = rect_getSquareDistance(ir.x, ir.y, ir.w, ir.h, ar.x, ar.y, ar.w, ar.h) + local bd = rect_getSquareDistance(ir.x, ir.y, ir.w, ir.h, br.x, br.y, br.w, br.h) + return ad < bd + end + return a.ti < b.ti +end + +local function addItemToCell(self, item, cx, cy) + self.rows[cy] = self.rows[cy] or setmetatable({}, { + __mode = 'v', + }) + local row = self.rows[cy] + row[cx] = row[cx] or { + itemCount = 0, + x = cx, + y = cy, + items = setmetatable({}, { + __mode = 'k', + }), + } + local cell = row[cx] + self.nonEmptyCells[cell] = true + if not cell.items[item] then + cell.items[item] = true + cell.itemCount = cell.itemCount + 1 + end +end + +local function removeItemFromCell(self, item, cx, cy) + local row = self.rows[cy] + if not row or not row[cx] or not row[cx].items[item] then + return false + end + + local cell = row[cx] + cell.items[item] = nil + cell.itemCount = cell.itemCount - 1 + if cell.itemCount == 0 then + self.nonEmptyCells[cell] = nil + end + return true +end + +local function getDictItemsInCellRect(self, cl, ct, cw, ch) + local items_dict = {} + for cy = ct, ct + ch - 1 do + local row = self.rows[cy] + if row then + for cx = cl, cl + cw - 1 do + local cell = row[cx] + if cell and cell.itemCount > 0 then -- no cell.itemCount > 1 because tunneling + for item, _ in pairs(cell.items) do + items_dict[item] = true + end + end + end + end + end + + return items_dict +end + +local function getCellsTouchedBySegment(self, x1, y1, x2, y2) + + local cells, cellsLen, visited = {}, 0, {} + + grid_traverse(self.cellSize, x1, y1, x2, y2, function(cx, cy) + local row = self.rows[cy] + if not row then + return + end + local cell = row[cx] + if not cell or visited[cell] then + return + end + + visited[cell] = true + cellsLen = cellsLen + 1 + cells[cellsLen] = cell + end) + + return cells, cellsLen +end + +local function getInfoAboutItemsTouchedBySegment(self, x1, y1, x2, y2, filter) + local cells, len = getCellsTouchedBySegment(self, x1, y1, x2, y2) + local cell, rect, l, t, w, h, ti1, ti2, tii0, tii1 + local visited, itemInfo, itemInfoLen = {}, {}, 0 + for i = 1, len do + cell = cells[i] + for item in pairs(cell.items) do + if not visited[item] then + visited[item] = true + if (not filter or filter(item)) then + rect = self.rects[item] + l, t, w, h = rect.x, rect.y, rect.w, rect.h + + ti1, ti2 = rect_getSegmentIntersectionIndices(l, t, w, h, x1, y1, x2, y2, 0, 1) + if ti1 and ((0 < ti1 and ti1 < 1) or (0 < ti2 and ti2 < 1)) then + -- the sorting is according to the t of an infinite line, not the segment + tii0, tii1 = rect_getSegmentIntersectionIndices(l, t, w, h, x1, y1, x2, y2, -math.huge, + math.huge) + itemInfoLen = itemInfoLen + 1 + itemInfo[itemInfoLen] = { + item = item, + ti1 = ti1, + ti2 = ti2, + weight = min(tii0, tii1), + } + end + end + end + end + end + table.sort(itemInfo, sortByWeight) + return itemInfo, itemInfoLen +end + +local function getResponseByName(self, name) + local response = self.responses[name] + if not response then + error(('Unknown collision type: %s (%s)'):format(name, type(name))) + end + return response +end + +-- Misc Public Methods + +function World:addResponse(name, response) + self.responses[name] = response +end + +function World:project(item, x, y, w, h, goalX, goalY, filter) + assertIsRect(x, y, w, h) + + goalX = goalX or x + goalY = goalY or y + filter = filter or defaultFilter + + local collisions, len = {}, 0 + + local visited = {} + if item ~= nil then + visited[item] = true + end + + -- This could probably be done with less cells using a polygon raster over the cells instead of a + -- bounding rect of the whole movement. Conditional to building a queryPolygon method + local tl, tt = min(goalX, x), min(goalY, y) + local tr, tb = max(goalX + w, x + w), max(goalY + h, y + h) + local tw, th = tr - tl, tb - tt + + local cl, ct, cw, ch = grid_toCellRect(self.cellSize, tl, tt, tw, th) + + local dictItemsInCellRect = getDictItemsInCellRect(self, cl, ct, cw, ch) + + for other, _ in pairs(dictItemsInCellRect) do + if not visited[other] then + visited[other] = true + + local responseName = filter(item, other) + if responseName then + local ox, oy, ow, oh = self:getRect(other) + local col = rect_detectCollision(x, y, w, h, ox, oy, ow, oh, goalX, goalY) + + if col then + col.other = other + col.item = item + col.type = responseName + + len = len + 1 + collisions[len] = col + end + end + end + end + + table.sort(collisions, sortByTiAndDistance) + + return collisions, len +end + +function World:countCells() + local count = 0 + for _, row in pairs(self.rows) do + for _, _ in pairs(row) do + count = count + 1 + end + end + return count +end + +function World:hasItem(item) + return not not self.rects[item] +end + +function World:getItems() + local items, len = {}, 0 + for item, _ in pairs(self.rects) do + len = len + 1 + items[len] = item + end + return items, len +end + +function World:countItems() + local len = 0 + for _ in pairs(self.rects) do + len = len + 1 + end + return len +end + +function World:getRect(item) + local rect = self.rects[item] + if not rect then + error('Item ' .. tostring(item) .. + ' must be added to the world before getting its rect. Use world:add(item, x,y,w,h) to add it first.') + end + return rect.x, rect.y, rect.w, rect.h +end + +function World:toWorld(cx, cy) + return grid_toWorld(self.cellSize, cx, cy) +end + +function World:toCell(x, y) + return grid_toCell(self.cellSize, x, y) +end + +--- Query methods + +function World:queryRect(x, y, w, h, filter) + + assertIsRect(x, y, w, h) + + local cl, ct, cw, ch = grid_toCellRect(self.cellSize, x, y, w, h) + local dictItemsInCellRect = getDictItemsInCellRect(self, cl, ct, cw, ch) + + local items, len = {}, 0 + + local rect + for item, _ in pairs(dictItemsInCellRect) do + rect = self.rects[item] + if (not filter or filter(item)) and rect_isIntersecting(x, y, w, h, rect.x, rect.y, rect.w, rect.h) then + len = len + 1 + items[len] = item + end + end + + return items, len +end + +function World:queryPoint(x, y, filter) + local cx, cy = self:toCell(x, y) + local dictItemsInCellRect = getDictItemsInCellRect(self, cx, cy, 1, 1) + + local items, len = {}, 0 + + local rect + for item, _ in pairs(dictItemsInCellRect) do + rect = self.rects[item] + if (not filter or filter(item)) and rect_containsPoint(rect.x, rect.y, rect.w, rect.h, x, y) then + len = len + 1 + items[len] = item + end + end + + return items, len +end + +function World:querySegment(x1, y1, x2, y2, filter) + local itemInfo, len = getInfoAboutItemsTouchedBySegment(self, x1, y1, x2, y2, filter) + local items = {} + for i = 1, len do + items[i] = itemInfo[i].item + end + return items, len +end + +function World:querySegmentWithCoords(x1, y1, x2, y2, filter) + local itemInfo, len = getInfoAboutItemsTouchedBySegment(self, x1, y1, x2, y2, filter) + local dx, dy = x2 - x1, y2 - y1 + local info, ti1, ti2 + for i = 1, len do + info = itemInfo[i] + ti1 = info.ti1 + ti2 = info.ti2 + + info.weight = nil + info.x1 = x1 + dx * ti1 + info.y1 = y1 + dy * ti1 + info.x2 = x1 + dx * ti2 + info.y2 = y1 + dy * ti2 + end + return itemInfo, len +end + +--- Main methods + +function World:add(item, x, y, w, h) + local rect = self.rects[item] + if rect then + error('Item ' .. tostring(item) .. ' added to the world twice.') + end + assertIsRect(x, y, w, h) + + self.rects[item] = { + x = x, + y = y, + w = w, + h = h, + } + + local cl, ct, cw, ch = grid_toCellRect(self.cellSize, x, y, w, h) + for cy = ct, ct + ch - 1 do + for cx = cl, cl + cw - 1 do + addItemToCell(self, item, cx, cy) + end + end + + return item +end + +function World:remove(item) + local x, y, w, h = self:getRect(item) + + self.rects[item] = nil + local cl, ct, cw, ch = grid_toCellRect(self.cellSize, x, y, w, h) + for cy = ct, ct + ch - 1 do + for cx = cl, cl + cw - 1 do + removeItemFromCell(self, item, cx, cy) + end + end +end + +function World:update(item, x2, y2, w2, h2) + local x1, y1, w1, h1 = self:getRect(item) + w2, h2 = w2 or w1, h2 or h1 + assertIsRect(x2, y2, w2, h2) + + if x1 ~= x2 or y1 ~= y2 or w1 ~= w2 or h1 ~= h2 then + + local cellSize = self.cellSize + local cl1, ct1, cw1, ch1 = grid_toCellRect(cellSize, x1, y1, w1, h1) + local cl2, ct2, cw2, ch2 = grid_toCellRect(cellSize, x2, y2, w2, h2) + + if cl1 ~= cl2 or ct1 ~= ct2 or cw1 ~= cw2 or ch1 ~= ch2 then + + local cr1, cb1 = cl1 + cw1 - 1, ct1 + ch1 - 1 + local cr2, cb2 = cl2 + cw2 - 1, ct2 + ch2 - 1 + local cyOut + + for cy = ct1, cb1 do + cyOut = cy < ct2 or cy > cb2 + for cx = cl1, cr1 do + if cyOut or cx < cl2 or cx > cr2 then + removeItemFromCell(self, item, cx, cy) + end + end + end + + for cy = ct2, cb2 do + cyOut = cy < ct1 or cy > cb1 + for cx = cl2, cr2 do + if cyOut or cx < cl1 or cx > cr1 then + addItemToCell(self, item, cx, cy) + end + end + end + + end + + local rect = self.rects[item] + rect.x, rect.y, rect.w, rect.h = x2, y2, w2, h2 + + end +end + +function World:move(item, goalX, goalY, filter) + local actualX, actualY, cols, len = self:check(item, goalX, goalY, filter) + + self:update(item, actualX, actualY) + + return actualX, actualY, cols, len +end + +function World:check(item, goalX, goalY, filter) + filter = filter or defaultFilter + + local visited = { + [item] = true, + } + local visitedFilter = function(itm, other) + if visited[other] then + return false + end + return filter(itm, other) + end + + local cols, len = {}, 0 + + local x, y, w, h = self:getRect(item) + + local projected_cols, projected_len = self:project(item, x, y, w, h, goalX, goalY, visitedFilter) + + while projected_len > 0 do + local col = projected_cols[1] + len = len + 1 + cols[len] = col + + visited[col.other] = true + + local response = getResponseByName(self, col.type) + + goalX, goalY, projected_cols, projected_len = response(self, col, x, y, w, h, goalX, goalY, visitedFilter) + end + + return goalX, goalY, cols, len +end + +-- Public library functions + +bump.newWorld = function(cellSize) + cellSize = cellSize or 64 + assertIsPositiveNumber(cellSize, 'cellSize') + local world = setmetatable({ + cellSize = cellSize, + rects = {}, + rows = {}, + nonEmptyCells = {}, + responses = {}, + }, World_mt) + + world:addResponse('touch', touch) + world:addResponse('cross', cross) + world:addResponse('slide', slide) + world:addResponse('bounce', bounce) + + return world +end + +bump.rect = { + getNearestCorner = rect_getNearestCorner, + getSegmentIntersectionIndices = rect_getSegmentIntersectionIndices, + getDiff = rect_getDiff, + containsPoint = rect_containsPoint, + isIntersecting = rect_isIntersecting, + getSquareDistance = rect_getSquareDistance, + detectCollision = rect_detectCollision, +} + +bump.responses = { + touch = touch, + cross = cross, + slide = slide, + bounce = bounce, +} + +return bump diff --git a/framework/lualib/3rd/misc/corope.lua b/framework/lualib/3rd/misc/corope.lua new file mode 100644 index 0000000..bd1051c --- /dev/null +++ b/framework/lualib/3rd/misc/corope.lua @@ -0,0 +1,326 @@ +-- https://github.com/bakpakin/corope +local setmetatable = setmetatable +local create = coroutine.create +local status = coroutine.status +local resume = coroutine.resume +local yield = coroutine.yield +local running = coroutine.running +local type = type +local select = select +local assert = assert +local unpack = unpack or table.unpack + +-- Object definitions + +local Bundle = {} +local Bundle_mt = { + __index = Bundle, +} + +local Rope = {} +local Rope_mt = { + __index = Rope, +} + +local function newBundle(options) + options = options or {} + return setmetatable({ + ropes = {}, -- Active threads + signals = {}, -- Signal listeners + time = 0, + errhand = options.errhand or print, + }, Bundle_mt) +end + +local function newRope(fn, ...) + return setmetatable({ + thread = create(fn), + primer = { + args = {...}, + n = select('#', ...), + }, + }, Rope_mt) +end + +-- Bundle implementation + +-- TODO: Should ropes be allowed to change bundles? +function Bundle:suspend(rope) + assert(rope.bundle == self, 'rope does not belong to this bundle') + assert(not rope.paused, 'cannot suspend already suspended rope') + local ts = self.ropes + local index = rope.index + rope.paused = true + ts[index] = ts[#ts] + ts[index].index = index + ts[#ts] = nil +end + +function Bundle:resume(rope) + assert(rope.bundle == self, 'rope does not belong to this bundle') + assert(rope.paused, 'cannot resume active rope') + local ts = self.ropes + local newIndex = #ts + 1 + ts[newIndex] = rope + rope.index = newIndex + rope.paused = nil +end + +function Bundle:update(dseconds) + local ropes = self.ropes + local i = 1 + while i <= #ropes do + local rope = ropes[i] + local t = rope.thread + local stat, err + if rope.primer then + local n = rope.primer.n + local args = rope.primer.args + rope.primer = nil + stat, err = resume(t, rope, unpack(args, 1, n)) + else + stat, err = resume(t, dseconds, rope.signal) + end + if not stat then + local errhand = rope.errhand or self.errhand + errhand(err) + end + if status(t) == 'dead' then -- rope has finished + ropes[i] = ropes[#ropes] + ropes[i].index = i + ropes[#ropes] = nil + else + i = i + 1 + end + end +end + +function Bundle:rope(fn, ...) + local ropes = self.ropes + local index = #ropes + 1 + local rope = newRope(fn, ...) + rope.index = index + rope.bundle = self + ropes[index] = rope + return rope +end +Bundle_mt.__call = Bundle.rope + +-- Rope implmentation + +local timeUnitToSeconds = { + s = 1, + sec = 1, + seconds = 1, + second = 1, + ms = 0.001, + milliseconds = 0.0001, + millisecond = 0.0001, + min = 60, + min = 60, + mn = 60, + minutes = 60, + minute = 60, + h = 3600, + hs = 3600, + hours = 3600, + hour = 3600, +} + +local timeUnitFrames = { + f = true, + fs = true, + frames = true, + frame = true, +} + +local function checkCorrectCoroutine(rope) + if running() ~= rope.thread then + error('rope function called outside of dispatch function or inside coroutine', 3) + end +end + +function Rope:wait(time) + checkCorrectCoroutine(self) + if time == nil then + return yield() + end + local tp = type(time) + if tp == 'number' then + while (time > 0) do + time = time - yield() + end + elseif tp == 'string' then + local numstr, unit = time:match('^(.-)(%a*)$') + local num = tonumber(numstr) or 1 + if timeUnitFrames[unit] then + for i = 1, num do + yield() + end + else + local time = num * (timeUnitToSeconds[unit] or 1) + while (time > 0) do + time = time - yield() + end + end + else + local f = time + local time = 0 + while not f(time) do + time = time + yield() + end + end +end + +-- Generate ease functions - https://github.com/rxi/flux/ +local easeFunctions = {} +do + local expressions = { + quad = "p * p", + cubic = "p * p * p", + quart = "p * p * p * p", + quint = "p * p * p * p * p", + expo = "2 ^ (10 * (p - 1))", + sine = "-math.cos(p * (math.pi * .5)) + 1", + circ = "-(math.sqrt(1 - (p * p)) - 1)", + back = "p * p * (2.7 * p - 1.7)", + elastic = "-(2^(10 * (p - 1)) * math.sin((p - 1.075) * (math.pi * 2) / .3))", + } + + local function makeEaseFunction(str, expr) + local load = loadstring or load + return load("return function(p) " .. str:gsub("%$e", expr) .. " end")() + end + + local function generateEase(name, expression) + easeFunctions[name] = makeEaseFunction("return $e", expression) + easeFunctions[name .. "in"] = easeFunctions[name] + easeFunctions[name .. "out"] = makeEaseFunction([[ + p = 1 - p + return 1 - ($e) + ]], expression) + easeFunctions[name .. "inout"] = makeEaseFunction([[ + p = p * 2 + if p < 1 then + return .5 * ($e) + else + p = 2 - p + return .5 * (1 - ($e)) + .5 + end + ]], expression) + end + + for k, v in pairs(expressions) do + generateEase(k, v) + end + easeFunctions['linear'] = makeEaseFunction('return $e', 'p') +end + +function Rope:tween(options) + checkCorrectCoroutine(self) + local object = options.object + local key = options.key + local to = options.to + local ease = options.ease or 'linear' + local timenumstr, timeunit = options.time:match('^(.-)(%a*)$') + local timenum = tonumber(timenumstr) or 1 + local from = options.from or object[key] + local scale = to - from + ease = easeFunctions[ease] or ease + assert(type(ease) == 'function' or type(ease) == 'table', + 'expected valid name of callable object for easing function') + if timeUnitFrames[timeunit] then + local t = 0 + local dt = 1 / timenum + for frame = 1, timenum do + object[key] = from + scale * ease(t) + t = t + dt + yield() + end + else + object[key] = to + local tscale = 1 / (timenum * (timeUnitToSeconds[timeunit] or 1)) + local t = 0 + while t < 1 do + object[key] = from + scale * ease(t) + local dt = yield() + t = t + tscale * dt + end + end + object[key] = to +end + +function Rope:tweenFork(options) + return self.bundle(Rope.tween, options) +end + +function Rope:fork(fn, ...) + checkCorrectCoroutine(self) + return self.bundle(fn, ...) +end + +function Rope:listen(name) + checkCorrectCoroutine(self) + local bundle = self.bundle + local signals = bundle.signals + local slist = signals[name] + bundle:suspend(self) + if not slist then + slist = {} + signals[name] = slist + end + slist[#slist + 1] = self + local dt, sig = yield() + return sig +end + +function Rope:signal(name, data) + checkCorrectCoroutine(self) + local bundle = self.bundle + local signals = bundle.signals + local slist = signals[name] + if slist then + for i = 1, #slist do + local rope = slist[i] + bundle:resume(rope) + rope.signal = data + end + signals[name] = nil + end +end + +function Rope:parallel(...) + checkCorrectCoroutine(self) + local bundle = self.bundle + local n = select('#', ...) + local ropes = {} -- also used as signal + local function onDone(rope) + local pindex = rope.pindex + ropes[pindex] = ropes[#ropes] + ropes[pindex].pindex = pindex + ropes[#ropes] = nil + if #ropes == 0 then + rope:signal(ropes, false) -- no error + end + end + for i = 1, n do + local fn = select(i, ...) + local function wrappedfn(r) + fn(r) + onDone(r) + end + local rope = bundle(wrappedfn) + local function errhand(err) + for i = 1, #ropes do + bundle:suspend(ropes[i]) + end + rope:signal(ropes, err) -- we errored out + end + rope.errhand = errhand + rope.pindex = i + ropes[#ropes + 1] = rope + end + return self:listen(ropes) +end + +return newBundle diff --git a/framework/lualib/3rd/misc/mm.lua b/framework/lualib/3rd/misc/mm.lua new file mode 100644 index 0000000..5a08b5b --- /dev/null +++ b/framework/lualib/3rd/misc/mm.lua @@ -0,0 +1,725 @@ +-- https://github.com/nenofite/mm + + +-- Terminal color (and formatting) codes. +local C = { + e = '\27[0m', -- reset + + -- Text attributes. + br = '\27[1m', -- bright + di = '\27[2m', -- dim + it = '\27[3m', -- italics + un = '\27[4m', -- underscore + bl = '\27[5m', -- blink + re = '\27[7m', -- reverse + hi = '\27[8m', -- hidden + + -- Text colors. + k = '\27[30m', -- black + r = '\27[31m', -- red + g = '\27[32m', -- green + y = '\27[33m', -- yellow + b = '\27[34m', -- blue + m = '\27[35m', -- magenta + c = '\27[36m', -- cyan + w = '\27[37m', -- white + + -- Background colors. + _k = '\27[40m', -- black + _r = '\27[41m', -- red + _g = '\27[42m', -- green + _y = '\27[43m', -- yellow + _b = '\27[44m', -- blue + _m = '\27[45m', -- magenta + _c = '\27[46m', -- cyan + _w = '\27[47m', -- white +} + +-- If we're on Windows, set all colors to empty strings so we don't spam the +-- output with meaningless escape codes. +local ON_WINDOWS = string.find(package.path, '\\') ~= nil +if ON_WINDOWS then + for k, v in pairs(C) do + C[k] = '' + end +end + +local METATABLE = { + "", + colors = C.it .. C.y, +} +local INDENT = " " + +-- The default sequence separator. +local SEP = " " + +-- The open and close brackets can be any piece (notably, a sequence with +-- colors). The separator must be a plain string. +local BOPEN, BSEP, BCLOSE = 1, 2, 3 + +-- The default frame brackets and separator. +local BRACKETS = {{ + "{", + colors = C.br, +}, ",", { + "}", + colors = C.br, +}} + +local STR_HALF = 30 +local MAX_STR_LEN = STR_HALF * 2 + +-- Names to use for named references. The order is important; these are aligned +-- with the colors in `NAME_COLORS`. +local NAMES = {"Cherry", "Apple", "Lemon", "Blueberry", "Jam", "Cream", "Rhubarb", "Lime", "Butter", "Grape", + "Pomegranate", "Sugar", "Cinnamon", "Avocado", "Honey"} + +-- Colors to use for named references. Don't use black nor white. +local NAME_COLORS = {C.r, C.g, C.y, C.b, C.m, C.c} + +-- Reserved Lua keywords as a convenient look-up table. +local RESERVED = { + ['and'] = true, + ['break'] = true, + ['do'] = true, + ['else'] = true, + ['elseif'] = true, + ['end'] = true, + ['false'] = true, + ['for'] = true, + ['function'] = true, + ['goto'] = true, + ['if'] = true, + ['in'] = true, + ['local'] = true, + ['nil'] = true, + ['not'] = true, + ['or'] = true, + ['repeat'] = true, + ['return'] = true, + ['then'] = true, + ['true'] = true, + ['until'] = true, + ['while'] = true, +} + +-- +-- Namers +-- + +local function new_namer() + local index = 1 + local suffix = 1 + local color_index = 1 + + return function() + -- Pick the name. + local result = NAMES[index] + if suffix > 1 then + result = result .. " " .. tostring(suffix) + end + + index = index + 1 + if index > #NAMES then + index = 1 + suffix = suffix + 1 + end + + -- Pick the color. + local color = NAME_COLORS[color_index] + + color_index = color_index + 1 + if color_index > #NAME_COLORS then + color_index = 1 + end + + return { + result, + colors = C.un .. color, + } + end +end + +-- +-- Context +-- + +local function new_context() + return { + occur = {}, + named = {}, + next_name = new_namer(), + + prev_indent = '', + next_indent = INDENT, + line_len = 0, + max_width = 78, + + result = '', + } +end + +-- +-- Translating into pieces +-- + +-- Translaters take any Lua value and create pieces to represent them. +-- +-- Some values should only be serialized once, both to prevent cycles and to +-- prevent redundancy. Or in other cases, these values cannot be serialized +-- (such as functions) but if they appear multiple times we want to express +-- that they are the same. +-- +-- When a translater encounters such a value for the first time, it is +-- registered in the context in `occur`. The value is wrapped in a plain table +-- with the `id` field pointing to the original value. If the value is +-- serializable, such as a table, then the the `def` field contains the piece +-- to display. If it is unserializable or it is not the first time this value +-- has occurred, the `def` field is nil. +-- +-- In the cleaning stage, these `id` fields are replaced with their names. If a +-- `def` field is present, then a sequence is generated to define the name with +-- the piece. + +local translaters = {} +local translate, ident_friendly + +function translate(val, ctx) + -- Try to find a type-specific translater. + local by_type = translaters[type(val)] + + if by_type then + -- If there is a type-specific translater, call it. + return by_type(val, ctx) + end + + -- Otherwise perform the default translation. + + -- Check whether we've already encountered this value. + if ctx.occur[val] then + -- We have; give it a name if we haven't already. + if not ctx.named[val] then + ctx.named[val] = ctx.next_name() + end + + -- Return the value as a reference. + return { + id = val, + } + else + -- We haven't; mark it as encountered. + ctx.occur[val] = true + + -- Return the value as a definition. + return { + id = val, + def = tostring(val), + } + end +end + +translaters['function'] = function(val, ctx) + -- Check whether we've already encountered this function. + if ctx.occur[val] then + -- We have; give it a name if we haven't already. + if not ctx.named[val] then + ctx.named[val] = ctx.next_name() + end + else + -- We haven't; mark it as encountered. + ctx.occur[val] = true + end + + -- Return the unserialized function. + return { + id = val, + } +end + +function translaters.table(val, ctx) + -- Check whether we've already encountered this table. + if ctx.occur[val] then + -- We have; give it a name if we haven't already. + if not ctx.named[val] then + ctx.named[val] = ctx.next_name() + end + + -- Return the unserialized table. + return { + id = val, + } + else + -- We haven't; mark it as encountered. + ctx.occur[val] = true + + -- Construct the frame for this table. + local result = { + bracket = BRACKETS, + } + + -- The equals-sign between key and value. + local eq = { + "=", + colors = C.di, + } + + -- Represent the metatable, if present. + local mt = getmetatable(val) + if mt then + -- Translate the metatable. + mt = translate(mt, ctx) + table.insert(result, {METATABLE, eq, mt}) + end + + -- Represent the contents. + for k, v in pairs(val) do + -- If it is a string key which can be represented without quotes, leave + -- it plain. + if ident_friendly(k) then + -- Leave the key as it is. + k = { + k, + colors = C.m, + } + else + -- Otherwise translate the key. + k = translate(k, ctx) + end + + -- Translate the value. + v = translate(v, ctx) + + table.insert(result, {k, eq, v}) + end + + -- Wrap the result with its id. + return { + id = val, + def = result, + } + end +end + +function translaters.string(val, ctx) + if #val <= MAX_STR_LEN then + -- The string is short enough; display it all. + local a = string.format('%q', val) + a = string.gsub(a, '\n', 'n') + + return { + a, + colors = C.g, + } + else + -- The string is too long. Only show the start and end. + local a = string.format('%q', string.sub(val, 1, STR_HALF)) + a = string.gsub(a, '\n', 'n') + local b = string.format('%q', string.sub(val, -STR_HALF)) + b = string.gsub(b, '\n', 'n') + + return { + a, + { + "...", + colors = C.di, + }, + b, + colors = C.g, + sep = '', + tight = true, + } + end +end + +function translaters.number(val, ctx) + return { + tostring(val), + colors = C.m .. C.br, + } +end + +-- Check whether a value can be represented as a Lua identifier, without the +-- need for quotes or translation. +-- +-- If the value is not a string, this immediately returns false. Otherwise, the +-- string must be a valid Lua name: a sequence of letters, digits, and +-- underscores that doesn't start with a digit and isn't a reserved keyword. +-- +-- See http://www.lua.org/manual/5.3/manual.html#3.1 +function ident_friendly(val) + -- The value must be a string. + if type(val) ~= 'string' then + return false + end + + if string.find(val, '^[_%a][_%a%d]*$') then + -- The value is a Lua name; check if it is reserved. + if RESERVED[val] then + -- The value is a resreved keyword. + return false + else + -- The value is a valid name. + return true + end + else + -- The value is not a Lua name. + return false + end +end + +-- +-- Cleaning pieces +-- + +local function clean(piece, ctx) + if type(piece) == 'table' then + -- Check if it's an id reference. + if piece.id then + local name = ctx.named[piece.id] + local def = piece.def + + -- Check whether it has been given a name. + if name then + local header = { + "<", + type(piece.id), + " ", + name, + ">", + colors = C.it, + sep = '', + tight = true, + } + -- Named. Check whether the reference has a definition. + if def then + -- Create a sequence defining the name to the definition. + return {header, { + "is", + colors = C.di, + }, clean(piece.def, ctx)} + else + -- Show just the name. + return header + end + else + -- No name. Check whether the reference has a definition. + if def then + -- Display the definition without any header. + return clean(piece.def, ctx) + else + -- Display just the type. + return { + "<", + type(piece.id), + ">", + colors = C.it, + sep = '', + tight = true, + } + end + end + + -- Check if it's a frame. + elseif piece.bracket then + -- Clean each child. + for i, child in ipairs(piece) do + piece[i] = clean(child, ctx) + end + return piece + + -- Otherwise it's a sequence. + else + -- Clean each child. + for i, child in ipairs(piece) do + piece[i] = clean(child, ctx) + end + return piece + end + else + -- It's a plain value, not a table; no cleaning is needed. + return piece + end +end + +-- +-- Displaying pieces +-- + +-- Pieces are either frames (with brackets), sequences (no brackets), or +-- strings. + +-- Frames are displayed either short-form as { a = 1 } or long-form as +-- { +-- a = 1 +-- }. + +-- Declare all the local functions first, so they can refer to each other. +local min_len, display, display_frame, display_sequence, display_string, display_frame_short, display_frame_long, + newline, newline_no_indent, write, write_nolength, space_here, space_newline + +-- Dispatch based on the piece's type. +function display(piece, ctx) + if type(piece) == 'string' then + -- String. + return display_string(piece, ctx) + elseif piece.bracket then + -- Frame. + return display_frame(piece, ctx) + else + -- Sequence. + return display_sequence(piece, ctx) + end +end + +-- Display a frame. +function display_frame(frame, ctx) + if #frame == 0 then + -- If the frame is empty, just display the brackets. + local str = { + frame.bracket[BOPEN], + frame.bracket[BCLOSE], + sep = '', + tight = true, + } + return display(str, ctx) + end + + local ml = min_len(frame) + + -- Try to fit the frame short-form on this line. + if ml <= space_here(ctx) then + return display_frame_short(frame, ctx) + + -- Otherwise try to fit it short-form on the next line. + elseif ml <= space_newline(ctx) then + newline(ctx) + return display_frame_short(frame, ctx) + + -- Otherwise display it long-form. + else + return display_frame_long(frame, ctx) + end +end + +function display_frame_short(frame, ctx) + -- Short-form frames never wrap onto new lines, so we don't need to do any + -- length checking (it's already been done for us). + + -- Write the open bracket. + display(frame.bracket[BOPEN], ctx) + write(" ", ctx) + + -- Display the first child. + display(frame[1], ctx) + + -- Display the remaining children. + for i = 2, #frame do + local child = frame[i] + + -- Write the separator. + write(frame.bracket[BSEP], ctx) + write(" ", ctx) + + -- Display the child. + display(child, ctx) + end + + -- Write the close bracket. + write(" ", ctx) + display(frame.bracket[BCLOSE], ctx) +end + +function display_frame_long(frame, ctx) + -- Remember the original value of next_indent. + local old_old_indent = ctx.prev_indent + local old_indent = ctx.next_indent + + -- Display the open bracket. + display(frame.bracket[BOPEN], ctx) + + -- Increase the indentation. + ctx.prev_indent = old_indent + ctx.next_indent = old_indent .. INDENT + + -- For all but the last child... + for i = 1, #frame - 1 do + local child = frame[i] + + -- Start a new line with old indentation. + newline_no_indent(ctx) + write(old_indent, ctx) + + -- Display the child. + display(child, ctx) + + -- Write the separator. + write(frame.bracket[BSEP], ctx) + end + + -- For the last child... + do + local child = frame[#frame] + + -- Start a new line with old indentation. + newline_no_indent(ctx) + write(old_indent, ctx) + + -- Display the child. + display(child, ctx) + -- No separator. + end + + -- Write the close bracket. + newline_no_indent(ctx) + write(old_old_indent, ctx) + display(frame.bracket[BCLOSE], ctx) + + -- Return to the old indentation. + ctx.prev_indent = old_old_indent + ctx.next_indent = old_indent +end + +function display_sequence(piece, ctx) + if #piece > 0 then + -- Check if this is a tight sequence. + if piece.tight then + -- Try to fit the entire sequence on one line. + local ml = min_len(piece, ctx) + + -- If it won't fit here, but it would fit on the next line, then write it + -- on the next line; otherwise, write it here. + if ml > space_here(ctx) and ml <= space_newline(ctx) then + newline(ctx) + end + end + + -- Apply the colors, if given. + if piece.colors then + write_nolength(piece.colors, ctx) + end + + -- Display the first child. + display(piece[1], ctx) + + -- For each following children: + for i = 2, #piece do + local child = piece[i] + + -- Apply the colors, if given. + if piece.colors then + write_nolength(piece.colors, ctx) + end + + -- Write a separator. + write(piece.sep or SEP, ctx) + + -- Then display the child. + display(child, ctx) + end + + -- Reset the colors. + if piece.colors then + write_nolength(C.e, ctx) + end + end +end + +function display_string(piece, ctx) + local ml = min_len(piece) + + -- If it won't fit here, but it would fit on the next line, then write it on + -- the next line; otherwise, write it here. + if ml > space_here(ctx) and ml <= space_newline(ctx) then + newline(ctx) + end + + write(piece, ctx) +end + +-- The minimum length to display this piece, if it is placed all on one line. +function min_len(piece, ctx) + -- For strings, simply return their length. + if type(piece) == 'string' then + return #piece + end + + -- Otherwise, we have some calculations to do. + local result = 0 + + if piece.bracket then + -- This is a frame. + + -- If it's an empty frame, just the open and close brackets. + if #piece == 0 then + return min_len(piece.bracket[BOPEN]) + min_len(piece.bracket[BCLOSE]) + end + + -- Open and close brackets, plus a space for each. + result = result + min_len(piece.bracket[BOPEN]) + min_len(piece.bracket[BCLOSE]) + 2 + + -- A separator between each item, plus a space for each. + result = result + (#piece - 1) * (#piece.bracket[BSEP] + 1) + else + -- This is a sequence. + + -- If it's an empty sequence, then nothing. + if #piece == 0 then + return 0 + end + + -- A single separator between each item. + result = result + (#piece - 1) * #(piece.sep or SEP) + end + + -- For both frames and sequences: + -- Find the minimum length of each child. + for _, child in ipairs(piece) do + result = result + min_len(child, ctx) + end + + return result +end + +function newline(ctx) + ctx.result = ctx.result .. "\n" + ctx.line_len = 0 + write(ctx.next_indent, ctx) +end + +function newline_no_indent(ctx) + ctx.result = ctx.result .. "\n" + ctx.line_len = 0 +end + +function write(str, ctx) + ctx.result = ctx.result .. str + ctx.line_len = ctx.line_len + #str +end + +function write_nolength(str, ctx) + ctx.result = ctx.result .. str +end + +function space_here(ctx) + return math.max(0, ctx.max_width - ctx.line_len) +end + +function space_newline(ctx) + return math.max(0, ctx.max_width - #ctx.next_indent) +end + +-- +-- Main function +-- + +return function(val) + if val == nil then + print(nil) + else + local ctx = new_context() + local piece = translate(val, ctx) + piece = clean(piece, ctx) + display(piece, ctx) + print(C.e .. ctx.result .. C.e) + end +end diff --git a/framework/lualib/3rd/misc/shash.lua b/framework/lualib/3rd/misc/shash.lua new file mode 100644 index 0000000..70920f0 --- /dev/null +++ b/framework/lualib/3rd/misc/shash.lua @@ -0,0 +1,177 @@ +-- +-- https://github.com/rxi/shash +-- +-- +local shash = { + _version = "0.1.1", +} +shash.__index = shash + +function shash.new(cellsize) + local self = setmetatable({}, shash) + cellsize = cellsize or 64 + self.cellsize = cellsize + self.tablepool = {} + self.cells = {} + self.entities = {} + return self +end + +local function coord_to_key(x, y) + return x + y * 1e7 +end + +local function cell_position(cellsize, x, y) + return math.floor(x / cellsize), math.floor(y / cellsize) +end + +local function each_overlapping_cell(self, e, fn, ...) + local cellsize = self.cellsize + local sx, sy = cell_position(cellsize, e[1], e[2]) + local ex, ey = cell_position(cellsize, e[3], e[4]) + for y = sy, ey do + for x = sx, ex do + local idx = coord_to_key(x, y) + fn(self, idx, ...) + end + end +end + +local function add_entity_to_cell(self, idx, e) + if not self.cells[idx] then + self.cells[idx] = {e} + else + table.insert(self.cells[idx], e) + end +end + +local function remove_entity_from_cell(self, idx, e) + local t = self.cells[idx] + local n = #t + -- Only one entity? Remove entity from cell and remove cell + if n == 1 then + self.cells[idx] = nil + return + end + -- Find and swap-remove entity + for i, v in ipairs(t) do + if v == e then + t[i] = t[n] + t[n] = nil + return + end + end +end + +function shash:add(obj, x, y, w, h) + -- Create entity. The table is used as an array as this offers a noticable + -- performance increase on LuaJIT; the indices are as follows: + -- [1] = left, [2] = top, [3] = right, [4] = bottom, [5] = object + local e = {x, y, x + w, y + h, obj} + -- Add to main entities table + self.entities[obj] = e + -- Add to cells + each_overlapping_cell(self, e, add_entity_to_cell, e) +end + +function shash:remove(obj) + -- Get entity of obj + local e = self.entities[obj] + -- Remove from main entities table + self.entities[obj] = nil + -- Remove from cells + each_overlapping_cell(self, e, remove_entity_from_cell, e) +end + +function shash:update(obj, x, y, w, h) + -- Get entity from obj + local e = self.entities[obj] + -- No width/height specified? Get width/height from existing bounding box + w = w or e[3] - e[1] + h = h or e[4] - e[2] + -- Check the entity has actually changed cell-position, if it hasn't we don't + -- need to touch the cells at all + local cellsize = self.cellsize + local ax1, ay1 = cell_position(cellsize, e[1], e[2]) + local ax2, ay2 = cell_position(cellsize, e[3], e[4]) + local bx1, by1 = cell_position(cellsize, x, y) + local bx2, by2 = cell_position(cellsize, x + w, y + h) + local dirty = ax1 ~= bx1 or ay1 ~= by1 or ax2 ~= bx2 or ay2 ~= by2 + -- Remove from old cells + if dirty then + each_overlapping_cell(self, e, remove_entity_from_cell, e) + end + -- Update entity + e[1], e[2], e[3], e[4] = x, y, x + w, y + h + -- Add to new cells + if dirty then + each_overlapping_cell(self, e, add_entity_to_cell, e) + end +end + +function shash:clear() + -- Clear all cells and entities + for k in pairs(self.cells) do + self.cells[k] = nil + end + for k in pairs(self.entities) do + self.entities[k] = nil + end +end + +local function overlaps(e1, e2) + return e1[3] > e2[1] and e1[1] < e2[3] and e1[4] > e2[2] and e1[2] < e2[4] +end + +local function each_overlapping_in_cell(self, idx, e, set, fn, ...) + local t = self.cells[idx] + if not t then + return + end + for i, v in ipairs(t) do + if e ~= v and overlaps(e, v) and not set[v] then + fn(v[5], ...) + set[v] = true + end + end +end + +local function each_overlapping_entity(self, e, fn, ...) + -- Init set for keeping track of which entities have already been handled + local set = table.remove(self.tablepool) or {} + -- Do overlap checks + each_overlapping_cell(self, e, each_overlapping_in_cell, e, set, fn, ...) + -- Clear set and return to pool + for v in pairs(set) do + set[v] = nil + end + table.insert(self.tablepool, set) +end + +function shash:each(x, y, w, h, fn, ...) + local e = self.entities[x] + if e then + -- Got object, use its entity + each_overlapping_entity(self, e, y, w, h, fn, ...) + else + -- Got bounding box, make temporary entity + each_overlapping_entity(self, {x, y, x + w, y + h}, fn, ...) + end +end + +function shash:info(opt, ...) + if opt == "cells" or opt == "entities" then + local n = 0 + for _ in pairs(self[opt]) do + n = n + 1 + end + return n + end + if opt == "cell" then + local t = self.cells[coord_to_key(...)] + return t and #t or 0 + end + error(string.format("invalid opt '%s'", opt)) +end + +return shash diff --git a/framework/lualib/3rd/misc/treap.lua b/framework/lualib/3rd/misc/treap.lua new file mode 100644 index 0000000..61adb1a --- /dev/null +++ b/framework/lualib/3rd/misc/treap.lua @@ -0,0 +1,138 @@ +------------------ +-- *treap.lua*, a simple treap data structure implemented in Lua. +-- Source on [Github](http://github.com/Yonaba/treap.lua) +-- Private definitions +local random = math.random + +-- Performs right rotation on node y +local function rightRotate(y) + local x = y.left + local tail = x.right + x.right = y + y.left = tail + return x +end + +-- Performs left rotation on node x +local function leftRotate(x) + local y = x.right + local tail = y.left + y.left = x + x.right = tail + return y +end + +------------------------------- Module functions ------------------------------ + +--- Creates a treap node. +-- @name node +-- @param key a key +-- @param[opt] priority a numeric priority for the given key. Defaults to random value. +-- @return a node +local function newTreap(key, priority) + return { + key = key, + priority = priority or random(), + left = nil, + right = nil, + } +end + +--- Finds a key in the treap +-- @name find +-- @param root a root node in the treap +-- @param key a key +-- @return a node (or nil) +local function find(root, key) + if root == nil or root.key == key then + return root + end + if root.key < key then + return find(root.right, key) + end + return find(root.left, key) +end + +--- Inserts a key in the treap +-- @name insert +-- @param root a root node in the treap +-- @param key a key +-- @return the root node +local function insert(root, key, priority) + if root == nil then + return newTreap(key, priority) + end + if key <= root.key then + root.left = insert(root.left, key) + if root.left.priority > root.priority then + root = rightRotate(root) + end + else + root.right = insert(root.right, key) + if root.right.priority > root.priority then + root = leftRotate(root) + end + end + return root +end + +--- Deletes a key in the treap +-- @name delete +-- @param root a root node in the treap +-- @param key a key +-- @return the root node +local function delete(root, key) + if root == nil then + return root + end + if key < root.key then + root.left = delete(root.left, key) + elseif key > root.key then + root.right = delete(root.right, key) + elseif root.left == nil then + root = root.right + elseif root.right == nil then + root = root.left + elseif root.left.priority < root.right.priority then + root = leftRotate(root) + root.left = delete(root.left, key) + else + root = rightRotate(root) + root.right = delete(root.right, key) + end + return root +end + +--- In-order traversal. It maps `f (node, ...)` on every node along the traversal. +-- @name inorder +-- @param root a root node in the treap +-- @param f a function, defined as `f (node, ...)` +-- @param[opt] ... optional arguments to `f` +local function inorder(root, f, ...) + if root == nil then + return + end + inorder(root.left, f, ...) + f(root, ...) + inorder(root.right, f, ...) +end + +--- Returns the treap size. +-- @name size +-- @param root a root node in the treap +local function size(root) + local n = 0 + inorder(root, function() + n = n + 1 + end) + return n +end + +return { + new = newTreap, + insert = insert, + delete = delete, + find = find, + inorder = inorder, + size = size, +}