package scene import ( "context" "git.aiterp.net/lucifer/new-server/app/config" "git.aiterp.net/lucifer/new-server/models" "log" "sync" "time" ) type Manager struct { mu sync.Mutex allocations map[int]*Scene devices map[int]*models.Device scenes []*Scene notifyCh chan struct{} } func (mgr *Manager) UpdateDevice(ctx context.Context, device *models.Device, data *models.Scene) error { mgr.mu.Lock() defer mgr.mu.Unlock() scene, err := mgr.findScene(ctx, device.SceneAssignments, data) if err != nil { return err } if mgr.allocations[device.ID] != nil && mgr.allocations[device.ID] != scene { mgr.allocations[device.ID].RemoveDevice(*device) } if mgr.allocations[device.ID] != scene { if mgr.allocations == nil { mgr.allocations = make(map[int]*Scene, 8) } mgr.allocations[device.ID] = scene if scene != nil { scene.due = true } } if scene != nil { scene.UpsertDevice(*device) if mgr.devices == nil { mgr.devices = make(map[int]*models.Device) } mgr.devices[device.ID] = device } else { config.PublishChannel <- []models.Device{*device} } if mgr.notifyCh != nil { close(mgr.notifyCh) mgr.notifyCh = nil } return nil } func (mgr *Manager) UpdateScene(data *models.Scene) { mgr.mu.Lock() for _, scene := range mgr.scenes { if scene.data.ID == data.ID { scene.UpdateScene(scene.data) } } mgr.mu.Unlock() } func (mgr *Manager) FilterUnassigned(devices []models.Device) []models.Device { mgr.mu.Lock() defer mgr.mu.Unlock() res := make([]models.Device, 0, len(devices)) for _, device := range devices { if mgr.allocations[device.ID] == nil { res = append(res, device) } } return res } func (mgr *Manager) Run(ctx context.Context) { devices, err := config.DeviceRepository().FetchByReference(ctx, models.RKAll, "") if err == nil { for _, device := range devices { _ = mgr.UpdateDevice(ctx, &device, nil) } } ticker := time.NewTicker(time.Millisecond * 101) defer ticker.Stop() mgr.mu.Lock() mgr.notifyCh = make(chan struct{}) mgr.mu.Unlock() for { mgr.mu.Lock() if mgr.notifyCh == nil { mgr.notifyCh = make(chan struct{}) } notifyCh := mgr.notifyCh mgr.mu.Unlock() select { case <-ticker.C: case <-notifyCh: case <-ctx.Done(): return } timeout, cancel := context.WithTimeout(ctx, time.Millisecond*100) var updates []models.Device mgr.mu.Lock() for _, device := range mgr.devices { if device == nil { continue } if mgr.reScene(timeout, device) { updates = append(updates, *device) } } for _, scene := range mgr.scenes { if scene.Due() { scene.Run() } updates = append(updates, scene.Flush()...) } mgr.mu.Unlock() cancel() if len(updates) > 0 { log.Println("Updating", len(updates), "devices") config.PublishChannel <- updates } } } func (mgr *Manager) reScene(ctx context.Context, device *models.Device) bool { scene, err := mgr.findScene(ctx, device.SceneAssignments, nil) if err != nil { return false } if mgr.allocations[device.ID] != nil && mgr.allocations[device.ID] != scene { mgr.allocations[device.ID].RemoveDevice(*device) } if mgr.allocations[device.ID] != scene { if mgr.allocations == nil { mgr.allocations = make(map[int]*Scene, 8) } mgr.allocations[device.ID] = scene if scene == nil { return true } mgr.allocations[device.ID].UpsertDevice(*device) } return false } func (mgr *Manager) findScene(ctx context.Context, assignments []models.DeviceSceneAssignment, data *models.Scene) (*Scene, error) { if len(assignments) == 0 { return nil, nil } var selected *models.DeviceSceneAssignment for _, assignment := range assignments { pos := int64(time.Since(assignment.StartTime) / time.Millisecond) if assignment.DurationMS == 0 || (pos >= -1 && pos < assignment.DurationMS) { selected = &assignment } } if selected == nil { return nil, nil } for _, scene := range mgr.scenes { if scene.group == selected.Group && scene.startTime.Equal(selected.StartTime) && selected.DurationMS == scene.duration.Milliseconds() { return scene, nil } } if data == nil { for i := range mgr.scenes { if mgr.scenes[i].data.ID == selected.SceneID { data = &mgr.scenes[i].data break } } } if data == nil { var err error data, err = config.SceneRepository().Find(ctx, selected.SceneID) if err != nil { return nil, err } } scene := &Scene{ startTime: selected.StartTime, duration: time.Duration(selected.DurationMS) * time.Millisecond, lastRun: 0, due: true, group: selected.Group, data: *data, roleList: make([][]models.Device, len(data.Roles)), deviceMap: make(map[int]*models.Device, 8), roleMap: make(map[int]int, 8), changes: make(map[int]models.NewDeviceState, 8), } mgr.scenes = append(mgr.scenes, scene) return scene, nil } var globalManager = &Manager{} func GlobalManager() *Manager { return globalManager }