automated snapshot

This commit is contained in:
sumi
2025-12-15 01:25:28 -06:00
parent f46bb4d17b
commit a66ea07961
9 changed files with 430 additions and 90 deletions

2
.gitignore vendored
View File

@@ -1,3 +1,5 @@
vendor/
result
snapshots/
snapshot.png

4
db.go Normal file
View File

@@ -0,0 +1,4 @@
package main
type DB struct {
}

12
db/schema.sql Normal file
View 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);

View File

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

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

@@ -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
View 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:])
}

90
main.go
View File

@@ -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
@@ -74,11 +41,11 @@ func curve(order int, length float64, angle float64) {
rl.DrawLine(0, 0, len, 0, rl.Black)
rl.Translatef(float32(len), 0, 0)
} else {
curve(order - 1, length/2, -angle)
curve(order-1, length/2, -angle)
rl.Rotatef(float32(angle), 0, 0, 1)
curve(order - 1, length/2, angle)
curve(order-1, length/2, angle)
rl.Rotatef(float32(angle), 0, 0, 1)
curve(order - 1, length/2, -angle)
curve(order-1, length/2, -angle)
}
}
@@ -87,11 +54,22 @@ 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 {
var camera = rl.Camera2D{
Target: rl.Vector2{X: 0, Y: 0},
Offset: rl.Vector2{X: float32(screenWidth) / 2, Y: float32(screenHeight) / 2},
Rotation: 0,
@@ -103,8 +81,7 @@ func main() {
angles := make([]float32, 1000)
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])
angles[i] = float32(noise.Eval2(float64(i)*0.05, 0.00))*0.1 - 0.05
}
frameNum := 0
@@ -161,10 +138,10 @@ func main() {
// initial transform by halfway again through angle array
angleIndex := frameNum%len(angles)
angleIndex := frameNum % len(angles)
angle := angles[angleIndex]
initAngle := angles[(angleIndex + len(angles)/2)%len(angles)]
initAngle := angles[(angleIndex+len(angles)/2)%len(angles)]
rl.Rotatef(2500*initAngle, 0, 0, 1)
rl.Translatef(100*initAngle, 100*initAngle, 0)
@@ -174,17 +151,16 @@ func main() {
rl.Translatef(float32(stepSize), 0, 0)
rl.Rotatef(angle, 0, 0, 1)
angleIndex++
angleIndex = angleIndex%len(angles)
angleIndex = angleIndex % len(angles)
angle += angles[angleIndex]
}
rl.PopMatrix()
rl.PushMatrix()
//rl.Translatef(-screenWidth/2, screenHeight/2, 0)
sierpinskiArrow(9,800)
sierpinskiArrow(9, 800)
rl.PopMatrix()
@@ -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
View 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
}