Skip to content

Commit

Permalink
Improve performance
Browse files Browse the repository at this point in the history
  • Loading branch information
BarredEwe committed Dec 4, 2024
1 parent e04a012 commit 84fc8a2
Show file tree
Hide file tree
Showing 6 changed files with 158 additions and 51 deletions.
6 changes: 4 additions & 2 deletions SnowfallApp.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,7 @@
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = SnowfallApp/Info.plist;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.entertainment";
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
Expand All @@ -279,7 +280,7 @@
IPHONEOS_DEPLOYMENT_TARGET = 18.1;
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 15.0;
MACOSX_DEPLOYMENT_TARGET = 14;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = PrefireTools.SnowfallApp;
PRODUCT_NAME = "$(TARGET_NAME)";
Expand All @@ -306,6 +307,7 @@
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = SnowfallApp/Info.plist;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.entertainment";
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
Expand All @@ -319,7 +321,7 @@
IPHONEOS_DEPLOYMENT_TARGET = 18.1;
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 15.0;
MACOSX_DEPLOYMENT_TARGET = 14;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = PrefireTools.SnowfallApp;
PRODUCT_NAME = "$(TARGET_NAME)";
Expand Down
71 changes: 71 additions & 0 deletions SnowfallApp/Common/DisplayLink.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import AppKit

class DisplayLink {
let timer: CVDisplayLink
let source: DispatchSourceUserDataAdd

var callback : Optional<() -> ()> = nil

var running : Bool { CVDisplayLinkIsRunning(timer) }

init?(onQueue queue: DispatchQueue = DispatchQueue.main) {
source = DispatchSource.makeUserDataAddSource(queue: queue)
var timerRef : CVDisplayLink? = nil
var successLink = CVDisplayLinkCreateWithActiveCGDisplays(&timerRef)

if let timer = timerRef {
successLink = CVDisplayLinkSetOutputCallback(timer, {
(timer : CVDisplayLink, currentTime : UnsafePointer<CVTimeStamp>, outputTime : UnsafePointer<CVTimeStamp>, _ : CVOptionFlags, _ : UnsafeMutablePointer<CVOptionFlags>, sourceUnsafeRaw : UnsafeMutableRawPointer?) -> CVReturn in

if let sourceUnsafeRaw = sourceUnsafeRaw {
let sourceUnmanaged = Unmanaged<DispatchSourceUserDataAdd>.fromOpaque(sourceUnsafeRaw)
sourceUnmanaged.takeUnretainedValue().add(data: 1)
}

return kCVReturnSuccess

}, Unmanaged.passUnretained(source).toOpaque())

guard successLink == kCVReturnSuccess else {
NSLog("Failed to create timer with active display")
return nil
}

successLink = CVDisplayLinkSetCurrentCGDisplay(timer, CGMainDisplayID())

guard successLink == kCVReturnSuccess else {
NSLog("Failed to connect to display")
return nil
}

self.timer = timer
} else {
NSLog("Failed to create timer with active display")
return nil
}

source.setEventHandler {
[weak self] in self?.callback?()
}
}

func start() {
guard !running else { return }

CVDisplayLinkStart(timer)
source.resume()
}

func cancel() {
guard running else { return }

CVDisplayLinkStop(timer)
source.cancel()
}

deinit {
if running {
cancel()
}
}
}
10 changes: 6 additions & 4 deletions SnowfallApp/Common/Settings.swift
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import Cocoa

struct Settings {
static let screenSize = NSScreen.main?.frame.size ?? .zero
private static let screenSize = NSScreen.main?.frame.size ?? .zero
static let snowflakeSizeRange: ClosedRange<CGFloat> = 2...10
static let maxSnowflakes = Int((screenSize.height * screenSize.width) * 0.0001)
static let snowflakeSpeedRange: ClosedRange<CGFloat> = 1...5
static let windStrength: CGFloat = 0.5
static let maxSnowflakes = 1000
static let snowflakeSpeedRange: ClosedRange<CGFloat> = 1...3
static let windStrength: CGFloat = 0.3
}


81 changes: 36 additions & 45 deletions SnowfallApp/Common/SnowfallView.swift
Original file line number Diff line number Diff line change
@@ -1,79 +1,57 @@
import SwiftUI

struct SnowfallView: View {
@State private var screenSize: CGSize = NSScreen.main?.frame.size ?? .zero
@State private var snowflakes: [Snowflake] = []
@State private var mousePosition: CGPoint = .zero
static var mousePosition: CGPoint = .zero
static let link = DisplayLink()

var body: some View {
ZStack {
ForEach(snowflakes) { snowflake in
Circle()
.fill(Color.white.opacity(0.8))
.frame(width: snowflake.size, height: snowflake.size)
.position(x: snowflake.x, y: snowflake.y)
}
Snowflakes(snowflakes: $snowflakes)
}
.background(MouseTrackingView { position in
mousePosition = CGPoint(x: position.x, y: position.y)
SnowfallView.mousePosition = position
})
.onChange(of: NSScreen.main?.frame.size ?? .zero, initial: true) { oldValue, newValue in
guard screenSize != newValue else { return }

screenSize = newValue
}
.onAppear {
startSnowfall()
DispatchQueue.main.async {
setupWindow()
}
}
.ignoresSafeArea()
}

func setupWindow() {
guard let window = NSApplication.shared.windows.first else { return }

window.isOpaque = true
window.hasShadow = false
window.backgroundColor = .clear
window.level = .screenSaver
window.styleMask.remove(.closable)
window.styleMask.remove(.miniaturizable)
window.styleMask.remove(.resizable)
window.styleMask = [.borderless]
window.setFrame(NSScreen.main?.frame ?? .zero, display: true)
window.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary]
window.makeKeyAndOrderFront(nil)
window.ignoresMouseEvents = true
}

func startSnowfall() {
Timer.scheduledTimer(withTimeInterval: 0.05, repeats: true) { _ in
withAnimation(.linear(duration: 0.05)) {
updateSnowflakes()
}
SnowfallView.link?.callback = {
updateSnowflakes()
}
SnowfallView.link?.start()
}

func updateSnowflakes() {
snowflakes.removeAll { $0.y > Settings.screenSize.height }
snowflakes.removeAll { $0.y > screenSize.height }

snowflakes = snowflakes.map { snowflake in
var updatedSnowflake = snowflake

let dx = mousePosition.x - updatedSnowflake.x
let dy = mousePosition.y - updatedSnowflake.y
for i in snowflakes.indices {
let dx = SnowfallView.mousePosition.x - snowflakes[i].x
let dy = SnowfallView.mousePosition.y - snowflakes[i].y
let distance = hypot(dx, dy)

let influenceRadius = 100 * (1 - updatedSnowflake.speed / Settings.snowflakeSpeedRange.upperBound)
let influenceRadius = 100 * (1 - snowflakes[i].speed / Settings.snowflakeSpeedRange.upperBound)
let attractionStrength = 0.5

if distance < influenceRadius {
let scale = (influenceRadius - distance) / influenceRadius

updatedSnowflake.x -= dx * scale * attractionStrength
updatedSnowflake.y -= dy * scale * attractionStrength
snowflakes[i].x -= dx * scale * attractionStrength
snowflakes[i].y -= dy * scale * attractionStrength
}

updatedSnowflake.x += CGFloat.random(in: -1...1) * Settings.windStrength
snowflakes[i].x += CGFloat.random(in: 0.1...1) * Settings.windStrength
snowflakes[i].y += snowflakes[i].speed

updatedSnowflake.y += updatedSnowflake.speed
return updatedSnowflake
}

if snowflakes.count < Settings.maxSnowflakes {
Expand All @@ -83,10 +61,23 @@ struct SnowfallView: View {

func createSnowflake() -> Snowflake {
Snowflake(
x: CGFloat.random(in: 0...Settings.screenSize.width),
x: CGFloat.random(in: 0...screenSize.width),
y: -10,
size: CGFloat.random(in: Settings.snowflakeSizeRange),
speed: CGFloat.random(in: Settings.snowflakeSpeedRange)
)
}
}

struct Snowflakes: View {
@Binding var snowflakes: [Snowflake]

var body: some View {
ForEach(snowflakes) { snowflake in
Circle()
.fill(Color.white.opacity(0.2 * (Settings.snowflakeSizeRange.upperBound - snowflake.size)))
.frame(width: snowflake.size, height: snowflake.size)
.position(x: snowflake.x, y: snowflake.y)
}
}
}
21 changes: 21 additions & 0 deletions SnowfallApp/SnowfallApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,31 @@ import SwiftUI

@main
struct SnowfallAppApp: App {
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

var body: some Scene {
WindowGroup {
SnowfallView()
}
.commands { }
}
}

class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ notification: Notification) {
guard let window = NSApplication.shared.windows.first else { return }

window.isOpaque = true
window.hasShadow = false
window.backgroundColor = .clear
window.level = .screenSaver
window.styleMask.remove(.closable)
window.styleMask.remove(.miniaturizable)
window.styleMask.remove(.resizable)
window.styleMask = [.borderless]
window.setFrame(NSScreen.main?.frame ?? .zero, display: true)
window.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary]
window.makeKeyAndOrderFront(nil)
window.ignoresMouseEvents = true
}
}
20 changes: 20 additions & 0 deletions SnowfallApp/SnowfallShaders.metal
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#include <metal_stdlib>
using namespace metal;

struct Snowflake {
float2 position; // Позиция снежинки
float size; // Размер снежинки
};

kernel void snowflakeVertexShader(const device Snowflake* snowflakes [[ buffer(0) ]],
uint id [[ thread_position_in_grid ]],
device float4* outPosition [[ buffer(1) ]]) {
if (id < 100) { // Измените 100 на количество снежинок
float2 pos = snowflakes[id].position;
outPosition[id] = float4(pos.x, pos.y, 0.0, 1.0);
}
}

fragment float4 snowflakeFragmentShader(float4 inPosition [[ position ]]) {
return float4(1.0, 1.0, 1.0, 1.0); // Белый цвет снежинки
}

0 comments on commit 84fc8a2

Please sign in to comment.