FERS 0.1.0
The Flexible Extensible Radar Simulator
Loading...
Searching...
No Matches
assetSlice.ts
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-only
2// Copyright (c) 2025-present FERS Contributors (see AUTHORS.md).
3
4import { StateCreator } from 'zustand';
5import { defaultAntenna, defaultTiming, defaultWaveform } from '../defaults';
6import { generateSimId } from '../idUtils';
7import { createUniqueScenarioName } from '../nameUtils';
8import { cleanObject, serializeAntenna, serializeTiming } from '../serializers';
9import {
10 enqueueFullSyncDetached,
11 enqueueGranularSyncDetached,
12} from '../syncQueue';
13import { Antenna, AssetActions, ScenarioStore } from '../types';
14import { buildScenarioJson } from './backendSlice';
15
16export const createAssetSlice: StateCreator<
17 ScenarioStore,
18 [['zustand/immer', never]],
19 [],
20 AssetActions
21> = (set, get) => ({
22 addWaveform: () => {
23 set((state) => {
24 const id = generateSimId('Waveform');
25 state.waveforms.push({
26 ...defaultWaveform,
27 id,
28 name: createUniqueScenarioName(
29 state,
30 `Waveform ${state.waveforms.length + 1}`
31 ),
32 });
33 state.isDirty = true;
34 });
35 // libfers has no granular add API for Waveforms — full sync is required.
36 enqueueFullSyncDetached(() => buildScenarioJson(get()));
37 },
38 addTiming: () => {
39 set((state) => {
40 const id = generateSimId('Timing');
41 state.timings.push({
42 ...defaultTiming,
43 id,
44 name: createUniqueScenarioName(
45 state,
46 `Timing ${state.timings.length + 1}`
47 ),
48 });
49 state.isDirty = true;
50 });
51 // libfers has no granular add API for Timings — full sync is required.
52 enqueueFullSyncDetached(() => buildScenarioJson(get()));
53 },
54 addAntenna: () => {
55 set((state) => {
56 const id = generateSimId('Antenna');
57 state.antennas.push({
58 ...defaultAntenna,
59 id,
60 name: createUniqueScenarioName(
61 state,
62 `Antenna ${state.antennas.length + 1}`
63 ),
64 });
65 state.isDirty = true;
66 });
67 // libfers has no granular add API for Antennas — full sync is required.
68 enqueueFullSyncDetached(() => buildScenarioJson(get()));
69 },
70 addNoiseEntry: (timingId) => {
71 let touched = false;
72 set((state) => {
73 const timing = state.timings.find((t) => t.id === timingId);
74 if (timing) {
75 timing.noiseEntries.push({
76 id: generateSimId('Timing'),
77 alpha: 0,
78 weight: 0,
79 });
80 state.isDirty = true;
81 touched = true;
82 }
83 });
84 if (touched) {
85 const timing = get().timings.find((t) => t.id === timingId);
86 if (timing) {
87 enqueueGranularSyncDetached(
88 'Timing',
89 timing.id,
90 JSON.stringify(cleanObject(serializeTiming(timing)))
91 );
92 }
93 }
94 },
95 removeNoiseEntry: (timingId, entryId) => {
96 let touched = false;
97 set((state) => {
98 const timing = state.timings.find((t) => t.id === timingId);
99 if (timing) {
100 const index = timing.noiseEntries.findIndex(
101 (e) => e.id === entryId
102 );
103 if (index > -1) {
104 timing.noiseEntries.splice(index, 1);
105 state.isDirty = true;
106 touched = true;
107 }
108 }
109 });
110 if (touched) {
111 const timing = get().timings.find((t) => t.id === timingId);
112 if (timing) {
113 enqueueGranularSyncDetached(
114 'Timing',
115 timing.id,
116 JSON.stringify(cleanObject(serializeTiming(timing)))
117 );
118 }
119 }
120 },
121 setAntennaPattern: (antennaId, newPattern) => {
122 let touched = false;
123 set((state) => {
124 const index = state.antennas.findIndex((a) => a.id === antennaId);
125 if (index === -1) return;
126 delete state.antennaPreviewErrors[antennaId];
127
128 const oldAntenna = state.antennas[index];
129 const baseAntenna = {
130 id: oldAntenna.id,
131 type: oldAntenna.type,
132 name: oldAntenna.name,
133 efficiency: oldAntenna.efficiency,
134 meshScale: oldAntenna.meshScale,
135 design_frequency: oldAntenna.design_frequency,
136 };
137
138 let newAntennaState: Antenna;
139
140 switch (newPattern) {
141 case 'isotropic':
142 newAntennaState = {
143 ...baseAntenna,
144 pattern: 'isotropic',
145 };
146 break;
147 case 'sinc':
148 newAntennaState = {
149 ...baseAntenna,
150 pattern: 'sinc',
151 alpha: 1.0,
152 beta: 1.0,
153 gamma: 2.0,
154 };
155 break;
156 case 'gaussian':
157 newAntennaState = {
158 ...baseAntenna,
159 pattern: 'gaussian',
160 azscale: 1.0,
161 elscale: 1.0,
162 };
163 break;
164 case 'squarehorn':
165 case 'parabolic':
166 newAntennaState = {
167 ...baseAntenna,
168 pattern: newPattern,
169 diameter: 0.5,
170 };
171 break;
172 case 'xml':
173 case 'file':
174 newAntennaState = {
175 ...baseAntenna,
176 pattern: newPattern,
177 filename: '',
178 };
179 break;
180 }
181 state.antennas[index] = newAntennaState;
182 state.isDirty = true;
183 touched = true;
184 });
185 if (touched) {
186 const antenna = get().antennas.find((a) => a.id === antennaId);
187 if (antenna) {
188 enqueueGranularSyncDetached(
189 'Antenna',
190 antenna.id,
191 JSON.stringify(cleanObject(serializeAntenna(antenna)))
192 );
193 }
194 }
195 },
196});