• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

creativelifeform / three-nebula / 30002829029

23 Jul 2026 11:21AM UTC coverage: 72.664%. First build
30002829029

Pull #271

github

web-flow
Merge 805122179 into 552f198cc
Pull Request #271: fix(fromJSONAsync): preserve emitter + initializer input order

524 of 605 branches covered (86.61%)

Branch coverage included in aggregate %.

44 of 45 new or added lines in 1 file covered. (97.78%)

6406 of 8932 relevant lines covered (71.72%)

224.87 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.34
/src/core/fromJSONAsync.js
1
import * as Behaviour from '../behaviour';
2✔
2
import * as Initializer from '../initializer';
2✔
3

4
import { EULER, POOL_MAX } from '../constants';
2✔
5
import { DEFAULT_DAMPING } from '../emitter/constants';
2✔
6
import {
2✔
7
  INITIALIZER_TYPES_THAT_REQUIRE_THREE,
8
  SUPPORTED_JSON_BEHAVIOUR_TYPES,
9
  SUPPORTED_JSON_INITIALIZER_TYPES,
10
} from './constants';
11

12
import Rate from '../initializer/Rate';
2✔
13
import TextureInitializer from '../initializer/Texture';
2✔
14

15
const DEFAULT_OPTIONS = { shouldAutoEmit: true };
2✔
16

17
/**
2✔
18
 * Makes a rate instance.
2✔
19
 *
2✔
20
 * @param {object} json - The data required to construct a Rate instance
2✔
21
 * @return {Rate}
2✔
22
 */
2✔
23
const makeRate = json => Rate.fromJSON(json);
2✔
24

25
/**
2✔
26
 * Makes initializers from json items.
2✔
27
 *
2✔
28
 * @param {array<object>} items - An array of objects which provide initializer constructor params
2✔
29
 * @param {object} THREE - The Web GL Api to use
2✔
30
 * @return {array<Initializer>}
2✔
31
 */
2✔
32
const makeInitializers = (items, THREE) =>
2✔
33
  new Promise((resolve, reject) => {
34✔
34
    if (!items.length) {
34✔
35
      return resolve([]);
2✔
36
    }
2✔
37

38
    const numberOfInitializers = items.length;
32✔
39
    // Each result is written at its ORIGINAL index, and we resolve once every slot is
32✔
40
    // filled — so the resolved array preserves the input order regardless of how the
32✔
41
    // async texture loads below interleave. (Previously initializers were pushed as
32✔
42
    // they completed: non-texture ones first, then texture ones in load-resolution
32✔
43
    // order, which reordered them non-deterministically.)
32✔
44
    const madeInitializers = new Array(numberOfInitializers);
32✔
45
    let madeCount = 0;
32✔
46

47
    const onMade = (index, initializer) => {
32✔
48
      madeInitializers[index] = initializer;
118✔
49
      madeCount += 1;
118✔
50

51
      if (madeCount === numberOfInitializers) {
118✔
52
        return resolve(madeInitializers);
32✔
53
      }
32✔
54
    };
32✔
55

56
    items.forEach((data, index) => {
32✔
57
      const { type, properties } = data;
118✔
58

59
      if (!SUPPORTED_JSON_INITIALIZER_TYPES.includes(type)) {
118!
60
        return reject(
×
61
          `The initializer type ${type} is invalid or not yet supported`
×
62
        );
×
63
      }
×
64

65
      if (properties.texture) {
118✔
66
        const textureLoader = new THREE.TextureLoader();
32✔
67

68
        textureLoader.load(
32✔
69
          properties.texture,
32✔
70
          loadedTexture =>
32✔
71
            onMade(
32✔
72
              index,
32✔
73
              TextureInitializer.fromJSON({ ...properties, loadedTexture }, THREE)
32✔
74
            ),
32✔
75
          undefined,
32✔
76
          reject
32✔
77
        );
32✔
78

79
        return;
32✔
80
      }
32✔
81

82
      onMade(
86✔
83
        index,
86✔
84
        INITIALIZER_TYPES_THAT_REQUIRE_THREE.includes(type)
86✔
85
          ? Initializer[type].fromJSON(properties, THREE)
118!
86
          : Initializer[type].fromJSON(properties)
118✔
87
      );
118✔
88
    });
32✔
89
  });
2✔
90

91
/**
2✔
92
 * Makes behaviours from json items.
2✔
93
 *
2✔
94
 * @param {array<object>} items - An array of objects which provide behaviour constructor params
2✔
95
 * @return {Promise<array>}
2✔
96
 */
2✔
97
const makeBehaviours = items =>
2✔
98
  new Promise((resolve, reject) => {
68✔
99
    if (!items.length) {
68✔
100
      return resolve([]);
42✔
101
    }
42✔
102

103
    const numberOfBehaviours = items.length;
26✔
104
    const madeBehaviours = [];
26✔
105

106
    items.forEach(data => {
26✔
107
      const { type, properties } = data;
110✔
108

109
      if (!SUPPORTED_JSON_BEHAVIOUR_TYPES.includes(type)) {
110!
110
        return reject(
×
111
          `The behaviour type ${type} is invalid or not yet supported`
×
112
        );
×
113
      }
×
114

115
      madeBehaviours.push(Behaviour[type].fromJSON(properties));
110✔
116

117
      if (madeBehaviours.length === numberOfBehaviours) {
110✔
118
        return resolve(madeBehaviours);
26✔
119
      }
26✔
120
    });
26✔
121
  });
2✔
122

123
const makeEmitters = (emitters, Emitter, THREE, shouldAutoEmit) =>
2✔
124
  new Promise((resolve, reject) => {
20✔
125
    if (!emitters.length) {
20!
126
      return resolve([]);
×
127
    }
×
128

129
    const numberOfEmitters = emitters.length;
20✔
130

131
    if (!numberOfEmitters) {
20!
NEW
132
      return resolve([]);
×
133
    }
×
134

135
    // Each built emitter is written at its ORIGINAL index, and we resolve once every
20✔
136
    // slot is filled — so system.emitters preserves the input order regardless of how
20✔
137
    // the emitters' async initializer/texture loads interleave. (Previously emitters
20✔
138
    // were pushed as they completed, i.e. in load-resolution order.)
20✔
139
    const madeEmitters = new Array(numberOfEmitters);
20✔
140
    let madeCount = 0;
20✔
141

142
    emitters.forEach((data, index) => {
20✔
143
      const emitter = new Emitter();
34✔
144
      const {
34✔
145
        rate,
34✔
146
        rotation,
34✔
147
        initializers,
34✔
148
        behaviours,
34✔
149
        emitterBehaviours = [],
34✔
150
        position,
34✔
151
        totalEmitTimes = Infinity,
34✔
152
        life = Infinity,
34✔
153
        damping = DEFAULT_DAMPING,
34✔
154
      } = data;
34✔
155

156
      emitter.damping = damping;
34✔
157
      emitter
34✔
158
        .setRate(makeRate(rate))
34✔
159
        .setRotation(rotation)
34✔
160
        .setPosition(position);
34✔
161

162
      makeInitializers(initializers, THREE)
34✔
163
        .then(madeInitializers => {
34✔
164
          emitter.setInitializers(madeInitializers);
34✔
165

166
          return makeBehaviours(behaviours);
34✔
167
        })
34✔
168
        .then(madeBehaviours => {
34✔
169
          emitter.setBehaviours(madeBehaviours);
34✔
170

171
          return makeBehaviours(emitterBehaviours);
34✔
172
        })
34✔
173
        .then(madeEmitterBehaviours => {
34✔
174
          emitter.setEmitterBehaviours(madeEmitterBehaviours);
34✔
175

176
          return Promise.resolve(emitter);
34✔
177
        })
34✔
178
        .then(emitter => {
34✔
179
          madeEmitters[index] = shouldAutoEmit
34✔
180
            ? emitter.emit(totalEmitTimes, life)
34✔
181
            : emitter.setTotalEmitTimes(totalEmitTimes).setLife(life);
34✔
182
          madeCount += 1;
34✔
183

184
          if (madeCount === numberOfEmitters) {
34✔
185
            return resolve(madeEmitters);
20✔
186
          }
20✔
187
        })
34✔
188
        .catch(reject);
34✔
189
    });
20✔
190
  });
2✔
191

192
/**
2✔
193
 * Creates a System instance from a JSON object.
2✔
194
 *
2✔
195
 * @param {object} json - The JSON to create the System instance from
2✔
196
 * @param {number} json.preParticles - The predetermined number of particles
2✔
197
 * @param {string} json.integrationType - The integration algorithm to use
2✔
198
 * @param {array<object>} json.emitters - The emitters for the system instance
2✔
199
 * @param {object} THREE - The Web GL Api to use
2✔
200
 * @param {function} System - The system class
2✔
201
 * @param {function} Emitter - The emitter class
2✔
202
 * @param {object} [options={}] - Optional config options
2✔
203
 * @return {Promise<System>}
2✔
204
 */
2✔
205
export default (json, THREE, System, Emitter, options = {}) =>
2✔
206
  new Promise((resolve, reject) => {
20✔
207
    const {
20✔
208
      preParticles = POOL_MAX,
20✔
209
      integrationType = EULER,
20✔
210
      emitters = [],
20✔
211
    } = json;
20✔
212
    const system = new System(preParticles, integrationType);
20✔
213
    const { shouldAutoEmit } = { ...DEFAULT_OPTIONS, ...options };
20✔
214

215
    makeEmitters(emitters, Emitter, THREE, shouldAutoEmit)
20✔
216
      .then(madeEmitters => {
20✔
217
        const numberOfEmitters = madeEmitters.length;
20✔
218

219
        if (!numberOfEmitters) {
20!
220
          return resolve(system);
×
221
        }
×
222

223
        madeEmitters.forEach(madeEmitter => {
20✔
224
          system.addEmitter(madeEmitter);
34✔
225

226
          if (system.emitters.length === numberOfEmitters) {
34✔
227
            resolve(system);
20✔
228
          }
20✔
229
        });
20✔
230
      })
20✔
231
      .catch(reject);
20✔
232
  });
2✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc