Initial commit: NetworkCity — network traffic as a neon city
A native macOS network monitor that renders live traffic as a top-down neon city: your Mac is downtown, remote orgs are glowing districts, and traffic is cars of light driving the roads. Architecture: - NetworkCityCore: swappable data layer behind a ConnectionSource protocol (nettop-backed today), plus pure/tested logic — traffic diffing, org classification (reverse-DNS + CIDR), traffic classes, hub-and-spoke expansion policy, label anti-overlap, and latency→distance layout. - NetworkCityApp: SwiftUI + SpriteKit city — auto-expanding districts, protocol-coloured cars, click-to-inspect panel, and latency-as-distance (districts glide to their measured RTT). - nettop-probe: CLI proof of the data layer. 44 tests passing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
import SpriteKit
|
||||
import NetworkCityCore
|
||||
|
||||
/// How a car of light looks for a given traffic class + magnitude.
|
||||
struct CarStyle {
|
||||
let color: SKColor
|
||||
let scale: CGFloat
|
||||
let elongation: CGFloat // 1 = round dot; >1 = streak along motion
|
||||
let speed: CGFloat // points/sec
|
||||
}
|
||||
|
||||
/// Colour per traffic class — direction is read from motion, so hue is free to
|
||||
/// mean protocol.
|
||||
enum TrafficPalette {
|
||||
static func color(_ c: TrafficClass) -> SKColor {
|
||||
switch c {
|
||||
case .dns: return SKColor(red: 1.00, green: 0.82, blue: 0.25, alpha: 1) // gold
|
||||
case .https: return SKColor(red: 0.22, green: 0.88, blue: 1.00, alpha: 1) // cyan
|
||||
case .http: return SKColor(red: 1.00, green: 0.48, blue: 0.24, alpha: 1) // orange
|
||||
case .quic: return SKColor(red: 0.71, green: 0.48, blue: 1.00, alpha: 1) // violet
|
||||
case .other: return SKColor(red: 0.55, green: 0.60, blue: 0.72, alpha: 1) // slate
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Translate a class + this-tick byte volume into a car's personality. Heavy
|
||||
/// flows become "freight": bigger, slower, stretched into a glowing comet.
|
||||
func carStyle(_ cls: TrafficClass, bytes: UInt64) -> CarStyle {
|
||||
let heavy = bytes > 196_608 // ~96 KB/s over a 2s tick
|
||||
var scale: CGFloat
|
||||
var elongation: CGFloat = 1
|
||||
var speed: CGFloat = 230
|
||||
|
||||
switch cls {
|
||||
case .dns: scale = 0.055; speed = 360 // tiny, fast sparks
|
||||
case .https: scale = 0.085
|
||||
case .http: scale = 0.085
|
||||
case .quic: scale = 0.085; elongation = 2.4; speed = 285 // streaks
|
||||
case .other: scale = 0.065
|
||||
}
|
||||
|
||||
if heavy {
|
||||
scale *= 1.7
|
||||
elongation = max(elongation, 3.0) // freight comet
|
||||
speed *= 0.7 // slow and heavy
|
||||
}
|
||||
return CarStyle(color: TrafficPalette.color(cls), scale: scale, elongation: elongation, speed: speed)
|
||||
}
|
||||
|
||||
/// Neon-night palette. Bright, saturated colors on near-black so additive
|
||||
/// blending reads as glow.
|
||||
enum Palette {
|
||||
static let background = SKColor(red: 0.039, green: 0.055, blue: 0.102, alpha: 1) // #0a0e1a
|
||||
static let grid = SKColor(red: 0.13, green: 0.20, blue: 0.38, alpha: 1)
|
||||
static let hub = SKColor(red: 1.00, green: 0.85, blue: 0.62, alpha: 1) // warm downtown
|
||||
static let building = SKColor(red: 0.50, green: 0.82, blue: 0.78, alpha: 1) // teal
|
||||
static let road = SKColor(red: 0.17, green: 0.26, blue: 0.46, alpha: 1)
|
||||
static let download = SKColor(red: 0.22, green: 0.88, blue: 1.00, alpha: 1) // cyan, inbound
|
||||
static let upload = SKColor(red: 1.00, green: 0.62, blue: 0.30, alpha: 1) // amber, outbound
|
||||
}
|
||||
|
||||
extension CGFloat {
|
||||
func clamped(_ lo: CGFloat, _ hi: CGFloat) -> CGFloat { Swift.min(Swift.max(self, lo), hi) }
|
||||
}
|
||||
|
||||
enum Textures {
|
||||
/// A white radial-gradient disc (opaque center → transparent edge). Tint it
|
||||
/// per-node with `colorBlendFactor` + `.add` blend mode to get a glow.
|
||||
// Immutable after creation and only read; safe to share across actors.
|
||||
nonisolated(unsafe) static let softCircle: SKTexture = makeSoftCircle(diameter: 128)
|
||||
|
||||
private static func makeSoftCircle(diameter: Int) -> SKTexture {
|
||||
let space = CGColorSpaceCreateDeviceRGB()
|
||||
let ctx = CGContext(
|
||||
data: nil, width: diameter, height: diameter,
|
||||
bitsPerComponent: 8, bytesPerRow: 0, space: space,
|
||||
bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue
|
||||
)!
|
||||
let colors = [
|
||||
SKColor.white.cgColor,
|
||||
SKColor.white.withAlphaComponent(0).cgColor,
|
||||
] as CFArray
|
||||
let gradient = CGGradient(colorsSpace: space, colors: colors, locations: [0, 1])!
|
||||
let center = CGPoint(x: diameter / 2, y: diameter / 2)
|
||||
ctx.drawRadialGradient(
|
||||
gradient, startCenter: center, startRadius: 0,
|
||||
endCenter: center, endRadius: CGFloat(diameter) / 2, options: []
|
||||
)
|
||||
return SKTexture(cgImage: ctx.makeImage()!)
|
||||
}
|
||||
}
|
||||
|
||||
/// Deterministic FNV-1a hash so a given host always lands in the same place,
|
||||
/// independent of Swift's per-process `Hasher` seed.
|
||||
func stableHash(_ s: String) -> UInt64 {
|
||||
var h: UInt64 = 1469598103934665603
|
||||
for byte in s.utf8 { h = (h ^ UInt64(byte)) &* 1099511628211 }
|
||||
return h
|
||||
}
|
||||
Reference in New Issue
Block a user