automated snapshot
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,3 +1,5 @@
|
||||
vendor/
|
||||
result
|
||||
snapshots/
|
||||
snapshot.png
|
||||
|
||||
|
||||
12
db/schema.sql
Normal file
12
db/schema.sql
Normal file
@@ -0,0 +1,12 @@
|
||||
CREATE TABLE snapshots (
|
||||
id INTEGER PRIMARY KEY, -- 64-bit snowflake
|
||||
sid TEXT NOT NULL UNIQUE, -- base62 encoded id
|
||||
created_at INTEGER NOT NULL,
|
||||
branch TEXT NOT NULL,
|
||||
git_hash TEXT NOT NULL,
|
||||
committed BOOL NOT NULL,
|
||||
path TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX idx_snapshots_sid ON snapshots(sid);
|
||||
|
||||
19
flake.nix
19
flake.nix
@@ -56,9 +56,12 @@
|
||||
|
||||
src = pkgs.lib.cleanSourceWith {
|
||||
src = ./.;
|
||||
filter = path: type:
|
||||
let base = builtins.baseNameOf path;
|
||||
in base != "vendor" && base != ".git";
|
||||
filter =
|
||||
path: type:
|
||||
let
|
||||
base = builtins.baseNameOf path;
|
||||
in
|
||||
base != "vendor" && base != ".git";
|
||||
};
|
||||
|
||||
env.CGO_ENABLED = 1;
|
||||
@@ -66,12 +69,15 @@
|
||||
nativeBuildInputs = nativeDeps;
|
||||
buildInputs = raylibDeps;
|
||||
|
||||
vendorHash = "sha256-teooSdWKQ08cYn/yWMZ8JKuo4rGnV5QOt2Zxzp34Q+I=";
|
||||
#vendorHash = "sha256-HDfllPEKJZOtkSoasS1yDCyZrWihlkBVRstLkF8AHd0=";
|
||||
|
||||
# use this every time there's vendor changeO
|
||||
# vendorHash = pkgs.lib.fakeHash;
|
||||
vendorHash = pkgs.lib.fakeHash;
|
||||
|
||||
ldflags = [ "-s" "-w" ];
|
||||
ldflags = [
|
||||
"-s"
|
||||
"-w"
|
||||
];
|
||||
doCheck = false;
|
||||
};
|
||||
|
||||
@@ -82,6 +88,7 @@
|
||||
gopls
|
||||
delve
|
||||
gotools
|
||||
sqlite
|
||||
];
|
||||
|
||||
nativeBuildInputs = nativeDeps;
|
||||
|
||||
11
go.mod
11
go.mod
@@ -8,6 +8,7 @@ require (
|
||||
github.com/gen2brain/raylib-go/raylib v0.55.1
|
||||
github.com/go-git/go-git/v6 v6.0.0-20251212081956-e83cbb9651e8
|
||||
github.com/ojrac/opensimplex-go v1.0.2
|
||||
modernc.org/sqlite v1.40.1
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -15,17 +16,25 @@ require (
|
||||
github.com/ProtonMail/go-crypto v1.3.0 // indirect
|
||||
github.com/cloudflare/circl v1.6.1 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/ebitengine/purego v0.7.1 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/go-git/gcfg/v2 v2.0.2 // indirect
|
||||
github.com/go-git/go-billy/v6 v6.0.0-20251126203821-7f9c95185ee0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/kevinburke/ssh_config v1.4.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||
github.com/pjbgf/sha1cd v0.5.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/sergi/go-diff v1.4.0 // indirect
|
||||
golang.org/x/crypto v0.46.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
|
||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect
|
||||
golang.org/x/net v0.48.0 // indirect
|
||||
golang.org/x/sys v0.39.0 // indirect
|
||||
modernc.org/libc v1.66.10 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.11.0 // indirect
|
||||
)
|
||||
|
||||
49
go.sum
49
go.sum
@@ -13,6 +13,8 @@ github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/ebitengine/purego v0.7.1 h1:6/55d26lG3o9VCZX8lping+bZcmShseiqlh2bnUDiPA=
|
||||
github.com/ebitengine/purego v0.7.1/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ=
|
||||
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
|
||||
@@ -33,6 +35,10 @@ github.com/go-git/go-git/v6 v6.0.0-20251212081956-e83cbb9651e8 h1:9PLPn/icZJaDXE
|
||||
github.com/go-git/go-git/v6 v6.0.0-20251212081956-e83cbb9651e8/go.mod h1:XY/p4VJq0DwOVAAs+58NpHcQrqwHDEzMv4g8MBK7ZVA=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ=
|
||||
github.com/kevinburke/ssh_config v1.4.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
@@ -40,12 +46,18 @@ github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/ojrac/opensimplex-go v1.0.2 h1:l4vs0D+JCakcu5OV0kJ99oEaWJfggSc9jiLpxaWvSzs=
|
||||
github.com/ojrac/opensimplex-go v1.0.2/go.mod h1:NwbXFFbXcdGgIFdiA7/REME+7n/lOf1TuEbLiZYOWnM=
|
||||
github.com/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0=
|
||||
github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
|
||||
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
@@ -54,19 +66,52 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
|
||||
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o=
|
||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
|
||||
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
|
||||
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
|
||||
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
||||
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
|
||||
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
|
||||
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
|
||||
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
modernc.org/cc/v4 v4.26.5 h1:xM3bX7Mve6G8K8b+T11ReenJOT+BmVqQj0FY5T4+5Y4=
|
||||
modernc.org/cc/v4 v4.26.5/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||
modernc.org/ccgo/v4 v4.28.1 h1:wPKYn5EC/mYTqBO373jKjvX2n+3+aK7+sICCv4Fjy1A=
|
||||
modernc.org/ccgo/v4 v4.28.1/go.mod h1:uD+4RnfrVgE6ec9NGguUNdhqzNIeeomeXf6CL0GTE5Q=
|
||||
modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA=
|
||||
modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
|
||||
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
||||
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||
modernc.org/libc v1.66.10 h1:yZkb3YeLx4oynyR+iUsXsybsX4Ubx7MQlSYEw4yj59A=
|
||||
modernc.org/libc v1.66.10/go.mod h1:8vGSEwvoUoltr4dlywvHqjtAqHBaw0j1jI7iFBTAr2I=
|
||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
|
||||
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||
modernc.org/sqlite v1.40.1 h1:VfuXcxcUWWKRBuP8+BR9L7VnmusMgBNNnBYGEe9w/iY=
|
||||
modernc.org/sqlite v1.40.1/go.mod h1:9fjQZ0mB1LLP0GYrp39oOJXx/I2sxEnZtzCmEQIKvGE=
|
||||
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
|
||||
93
internal/ids/ids.go
Normal file
93
internal/ids/ids.go
Normal file
@@ -0,0 +1,93 @@
|
||||
// k ordered id generator
|
||||
package ids
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// 2024-07-03T00:00:00Z
|
||||
customEpochMs int64 = 1719964800000
|
||||
|
||||
seqBits = 8
|
||||
workerBits = 16
|
||||
timeBits = 40
|
||||
|
||||
seqMask = (1 << seqBits) - 1 // 0xFF
|
||||
workerMask = (1 << workerBits) - 1 // 0xFFFF
|
||||
timeMask = (int64(1) << timeBits) - 1 // low 40 bits
|
||||
|
||||
workerShift = seqBits
|
||||
timeShift = workerBits + seqBits
|
||||
)
|
||||
|
||||
// Generator is safe for concurrent use.
|
||||
type Generator struct {
|
||||
workerID uint64
|
||||
mu sync.Mutex
|
||||
lastMs int64
|
||||
sequence uint64
|
||||
}
|
||||
|
||||
func NewGenerator(workerID uint32) (*Generator, error) {
|
||||
if workerID > workerMask {
|
||||
return nil, errors.New("workerID too large for 16 bits")
|
||||
}
|
||||
return &Generator{workerID: uint64(workerID)}, nil
|
||||
}
|
||||
|
||||
func (g *Generator) Next() (uint64, error) {
|
||||
nowMs := time.Now().UTC().UnixMilli()
|
||||
delta := nowMs - customEpochMs
|
||||
if delta < 0 {
|
||||
return 0, errors.New("time is before custom epoch")
|
||||
}
|
||||
if delta > timeMask {
|
||||
return 0, errors.New("timestamp overflow (40-bit ms range exceeded)")
|
||||
}
|
||||
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
|
||||
if delta == g.lastMs {
|
||||
g.sequence = (g.sequence + 1) & seqMask
|
||||
if g.sequence == 0 {
|
||||
// sequence wrapped; wait for next millisecond
|
||||
for delta == g.lastMs {
|
||||
nowMs = time.Now().UTC().UnixMilli()
|
||||
delta = nowMs - customEpochMs
|
||||
}
|
||||
}
|
||||
} else {
|
||||
g.sequence = 0
|
||||
g.lastMs = delta
|
||||
}
|
||||
|
||||
id := (uint64(delta) << timeShift) |
|
||||
((g.workerID & workerMask) << workerShift) |
|
||||
(g.sequence & seqMask)
|
||||
|
||||
return id, nil
|
||||
}
|
||||
|
||||
// ---- Base62 ----
|
||||
|
||||
const base62Alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
||||
|
||||
func Base62Encode(x uint64) string {
|
||||
if x == 0 {
|
||||
return "0"
|
||||
}
|
||||
var buf [11]byte // 62^11 > 2^64, so max 11 chars
|
||||
i := len(buf)
|
||||
for x > 0 {
|
||||
r := x % 62
|
||||
x /= 62
|
||||
i--
|
||||
buf[i] = base62Alphabet[r]
|
||||
}
|
||||
return string(buf[i:])
|
||||
}
|
||||
|
||||
72
main.go
72
main.go
@@ -1,46 +1,13 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"log"
|
||||
"os"
|
||||
"github.com/gen2brain/raylib-go/raylib"
|
||||
"github.com/ojrac/opensimplex-go"
|
||||
"github.com/go-git/go-git/v6"
|
||||
"math"
|
||||
)
|
||||
|
||||
func HeadHash(repoPath string) (string, error) {
|
||||
r, err := git.PlainOpen(repoPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
ref, err := r.Head()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return ref.Hash().String(), nil
|
||||
}
|
||||
|
||||
func IsDirty(repoPath string) (bool, error) {
|
||||
r, err := git.PlainOpen(repoPath)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
wt, err := r.Worktree()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
status, err := wt.Status()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return !status.IsClean(), nil
|
||||
}
|
||||
|
||||
func clamp01(v float64) float64 {
|
||||
if v < 0 {
|
||||
return 0
|
||||
@@ -87,8 +54,19 @@ func main() {
|
||||
const (
|
||||
screenWidth = 1200
|
||||
screenHeight = 700
|
||||
snapshotsDir = "snapshots"
|
||||
)
|
||||
|
||||
os.MkdirAll(snapshotsDir, 0755)
|
||||
|
||||
log := log.New(os.Stdout, "", log.Ldate|log.Ltime|log.Lshortfile)
|
||||
|
||||
storage, err := NewStorage(snapshotsDir)
|
||||
if err != nil {
|
||||
log.Printf("Error loading storage: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
rl.InitWindow(screenWidth, screenHeight, "sumi sierpinski arrow")
|
||||
|
||||
var camera = rl.Camera2D{
|
||||
@@ -104,7 +82,6 @@ func main() {
|
||||
noise := opensimplex.NewNormalized(0)
|
||||
for i := range len(angles) {
|
||||
angles[i] = float32(noise.Eval2(float64(i)*0.05, 0.00))*0.1 - 0.05
|
||||
fmt.Printf("angles[%d] = %.2f\n", i, angles[i])
|
||||
}
|
||||
|
||||
frameNum := 0
|
||||
@@ -179,7 +156,6 @@ func main() {
|
||||
}
|
||||
rl.PopMatrix()
|
||||
|
||||
|
||||
rl.PushMatrix()
|
||||
|
||||
//rl.Translatef(-screenWidth/2, screenHeight/2, 0)
|
||||
@@ -199,25 +175,12 @@ func main() {
|
||||
rl.EndMode2D()
|
||||
|
||||
if rl.IsKeyDown(rl.KeySpace) {
|
||||
//rl.TakeScreenshot("snapshot.png")
|
||||
|
||||
img := rl.LoadImageFromScreen()
|
||||
defer rl.UnloadImage(img)
|
||||
rl.ExportImage(*img, "snapshot.png")
|
||||
|
||||
dflag, err := IsDirty(".")
|
||||
|
||||
if err == nil {
|
||||
|
||||
if dflag {
|
||||
fmt.Printf("working tree is dirty\n")
|
||||
} else {
|
||||
fmt.Printf("working tree is clean\n")
|
||||
}
|
||||
|
||||
hash, err := HeadHash(".")
|
||||
if err == nil {
|
||||
fmt.Printf("HEAD -> %s\n", hash)
|
||||
}
|
||||
if _, err := storage.Save(img); err != nil {
|
||||
log.Printf("Error saving snapshot: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,4 +191,3 @@ func main() {
|
||||
|
||||
rl.CloseWindow()
|
||||
}
|
||||
|
||||
|
||||
206
storage.go
Normal file
206
storage.go
Normal file
@@ -0,0 +1,206 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"github.com/d2fn/sumi/internal/ids"
|
||||
"github.com/go-git/go-git/v6"
|
||||
//"github.com/go-git/go-git/v5/plumbing"
|
||||
"github.com/go-git/go-git/v6/plumbing/object"
|
||||
"github.com/gen2brain/raylib-go/raylib"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
"database/sql"
|
||||
_ "modernc.org/sqlite" // pure Go, Nix-friendly
|
||||
)
|
||||
|
||||
type Storage struct {
|
||||
repoRoot string
|
||||
snapshotsDir string
|
||||
gen *ids.Generator
|
||||
db *sql.DB
|
||||
log *log.Logger
|
||||
}
|
||||
|
||||
func NewStorage(snapshotsDir string) (*Storage, error) {
|
||||
log := log.New(os.Stdout, "", log.Ldate|log.Ltime|log.Lshortfile)
|
||||
gen, _ := ids.NewGenerator(82)
|
||||
db, err := OpenDB(filepath.Join(snapshotsDir, "snapshots.db"), log)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s := Storage {
|
||||
repoRoot: ".",
|
||||
snapshotsDir: snapshotsDir,
|
||||
gen: gen,
|
||||
db: db,
|
||||
}
|
||||
return &s, nil
|
||||
}
|
||||
|
||||
func OpenDB(path string, log *log.Logger) (*sql.DB, error) {
|
||||
log.Printf("Opening sqlite db")
|
||||
first := false
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
log.Printf("Database not found, initializing")
|
||||
first = true
|
||||
}
|
||||
|
||||
db, err := sql.Open("sqlite", path)
|
||||
if err != nil {
|
||||
log.Printf("Error opening database at %s", path)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if first {
|
||||
log.Printf("Initializing empty db with schema", path)
|
||||
if err := initSchema(db); err != nil {
|
||||
db.Close()
|
||||
return nil, err
|
||||
}
|
||||
log.Printf("Error initializing schema: %v", err)
|
||||
}
|
||||
|
||||
return db, nil
|
||||
}
|
||||
|
||||
func initSchema(db *sql.DB) error {
|
||||
schema, err := os.ReadFile("db/schema.sql")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
if _, err := tx.Exec(string(schema)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
func (s *Storage) Save(img *rl.Image) (string, error) {
|
||||
id, _ := s.gen.Next()
|
||||
kid := ids.Base62Encode(id)
|
||||
path := filepath.Join(s.snapshotsDir, kid)
|
||||
os.MkdirAll(path, 0755)
|
||||
|
||||
snapshotPng := filepath.Join(path, fmt.Sprintf("%s.png", kid))
|
||||
|
||||
rl.ExportImage(*img, snapshotPng)
|
||||
|
||||
hash, branch, committed, err := CommitAllIfDirty(s.repoRoot, "automated snapshot")
|
||||
|
||||
if err != nil {
|
||||
s.log.Printf("Error getting working tree in a known clean state: %v", err)
|
||||
} else {
|
||||
s.log.Printf("Created commit %s on %s for snapshot %s", hash, branch, kid)
|
||||
}
|
||||
|
||||
_, err = s.db.Exec(`
|
||||
INSERT INTO snapshots (id, sid, created_at, branch, git_hash, committed, path)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
`,
|
||||
id,
|
||||
kid,
|
||||
time.Now().UnixMilli(),
|
||||
branch,
|
||||
hash,
|
||||
committed,
|
||||
path,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
s.log.Printf("Error inserting snapshot row into db: %v\n", err)
|
||||
}
|
||||
|
||||
s.log.Printf("Saved snapshot to %s\n", path)
|
||||
|
||||
return path, nil
|
||||
}
|
||||
|
||||
func HeadHash(repoPath string) (string, error) {
|
||||
r, err := git.PlainOpen(repoPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
ref, err := r.Head()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return ref.Hash().String(), nil
|
||||
}
|
||||
|
||||
func IsDirty(repoPath string) (bool, error) {
|
||||
r, err := git.PlainOpen(repoPath)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
wt, err := r.Worktree()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
status, err := wt.Status()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return !status.IsClean(), nil
|
||||
}
|
||||
|
||||
func CommitAllIfDirty(repoPath, message string) (commitHash string, branch string, committed bool, err error) {
|
||||
r, err := git.PlainOpen(repoPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Determine branch (may be empty if detached)
|
||||
ref, err := r.Head()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if ref.Name().IsBranch() {
|
||||
branch = ref.Name().Short()
|
||||
}
|
||||
|
||||
wt, err := r.Worktree()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
status, err := wt.Status()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if status.IsClean() {
|
||||
return "", branch, false, nil
|
||||
}
|
||||
|
||||
// Stage everything (git add -A)
|
||||
if err = wt.AddWithOptions(&git.AddOptions{All: true}); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
hash, err := wt.Commit(message, &git.CommitOptions{
|
||||
Author: &object.Signature{
|
||||
Name: "sumi",
|
||||
Email: "sumi@local",
|
||||
When: time.Now(),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return hash.String(), branch, true, nil
|
||||
}
|
||||
Reference in New Issue
Block a user