🐳chore(工具): 调整docker 文件

develop
cloudfreexiao 5 years ago
parent f2cbf8041b
commit f782003d1e

@ -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
https://github.com/Grokzen/docker-redis-cluster
本地 dns 系统:
https://github.com/mafintosh/dns-discovery
基于 gitea + drone + docker 的 CI 流程实践
https://zhuanlan.zhihu.com/p/266072740

@ -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

@ -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)

@ -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/"

@ -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

@ -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

@ -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

@ -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

@ -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 = {
"<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

@ -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

@ -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,
}
Loading…
Cancel
Save