Skip to content

Commit d99f2df

Browse files
committedFeb 28, 2025·
avm1: Replace GcCell with Gc in StageObject
This refactor replaces GcCell with Gc and uses interior mutability instead.
1 parent 9ea3b21 commit d99f2df

File tree

2 files changed

+70
-56
lines changed

2 files changed

+70
-56
lines changed
 

‎core/src/avm1/object/stage_object.rs

+60-46
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,18 @@ use crate::display_object::{
1212
};
1313
use crate::string::{AvmString, StringContext, WStr};
1414
use crate::types::Percent;
15-
use gc_arena::{Collect, GcCell, GcWeakCell, Mutation};
15+
use gc_arena::barrier::unlock;
16+
use gc_arena::lock::RefLock;
17+
use gc_arena::{Collect, Gc, GcWeak, Mutation};
1618
use ruffle_macros::istr;
19+
use std::cell::RefMut;
1720
use std::fmt;
1821
use swf::Twips;
1922

2023
/// A ScriptObject that is inherently tied to a display node.
2124
#[derive(Clone, Copy, Collect)]
2225
#[collect(no_drop)]
23-
pub struct StageObject<'gc>(GcCell<'gc, StageObjectData<'gc>>);
26+
pub struct StageObject<'gc>(Gc<'gc, StageObjectData<'gc>>);
2427

2528
#[derive(Collect)]
2629
#[collect(no_drop)]
@@ -34,13 +37,13 @@ pub struct StageObjectData<'gc> {
3437
/// The display node this stage object
3538
pub display_object: DisplayObject<'gc>,
3639

37-
text_field_bindings: Vec<TextFieldBinding<'gc>>,
40+
text_field_bindings: RefLock<Vec<TextFieldBinding<'gc>>>,
3841
}
3942

4043
impl<'gc> StageObject<'gc> {
4144
/// Create a weak reference to the underlying data of this `StageObject`
42-
pub fn as_weak(&self) -> GcWeakCell<'gc, StageObjectData<'gc>> {
43-
GcCell::downgrade(self.0)
45+
pub fn as_weak(self) -> GcWeak<'gc, StageObjectData<'gc>> {
46+
Gc::downgrade(self.0)
4447
}
4548

4649
/// Create a stage object for a given display node.
@@ -49,16 +52,28 @@ impl<'gc> StageObject<'gc> {
4952
display_object: DisplayObject<'gc>,
5053
proto: Object<'gc>,
5154
) -> Self {
52-
Self(GcCell::new(
55+
Self(Gc::new(
5356
context.gc(),
5457
StageObjectData {
5558
base: ScriptObject::new(context, Some(proto)),
5659
display_object,
57-
text_field_bindings: Vec::new(),
60+
text_field_bindings: RefLock::new(Vec::new()),
5861
},
5962
))
6063
}
6164

65+
fn text_field_bindings_mut(
66+
self,
67+
gc_context: &Mutation<'gc>,
68+
) -> RefMut<'gc, Vec<TextFieldBinding<'gc>>> {
69+
unlock!(
70+
Gc::write(gc_context, self.0),
71+
StageObjectData,
72+
text_field_bindings
73+
)
74+
.borrow_mut()
75+
}
76+
6277
/// Registers a text field variable binding for this stage object.
6378
/// Whenever a property with the given name is changed, we should change the text in the text field.
6479
pub fn register_text_field_binding(
@@ -67,9 +82,7 @@ impl<'gc> StageObject<'gc> {
6782
text_field: EditText<'gc>,
6883
variable_name: AvmString<'gc>,
6984
) {
70-
self.0
71-
.write(gc_context)
72-
.text_field_bindings
85+
self.text_field_bindings_mut(gc_context)
7386
.push(TextFieldBinding {
7487
text_field,
7588
variable_name,
@@ -80,16 +93,14 @@ impl<'gc> StageObject<'gc> {
8093
/// Does not place the text field on the unbound list.
8194
/// Caller is responsible for placing the text field on the unbound list, if necessary.
8295
pub fn clear_text_field_binding(self, gc_context: &Mutation<'gc>, text_field: EditText<'gc>) {
83-
self.0
84-
.write(gc_context)
85-
.text_field_bindings
96+
self.text_field_bindings_mut(gc_context)
8697
.retain(|binding| !DisplayObject::ptr_eq(text_field.into(), binding.text_field.into()));
8798
}
8899

89100
/// Clears all text field bindings from this stage object, and places the textfields on the unbound list.
90101
/// This is called when the object is removed from the stage.
91102
pub fn unregister_text_field_bindings(self, context: &mut UpdateContext<'gc>) {
92-
for binding in self.0.write(context.gc()).text_field_bindings.drain(..) {
103+
for binding in self.text_field_bindings_mut(context.gc()).drain(..) {
93104
binding.text_field.clear_bound_stage_object(context);
94105
context.unbound_text_fields.push(binding.text_field);
95106
}
@@ -106,7 +117,6 @@ impl<'gc> StageObject<'gc> {
106117
} else if name.eq_with_case(b"_parent", case_sensitive) {
107118
return Some(
108119
self.0
109-
.read()
110120
.display_object
111121
.avm1_parent()
112122
.map(|dn| dn.object().coerce_to_object(activation))
@@ -166,17 +176,16 @@ struct TextFieldBinding<'gc> {
166176

167177
impl fmt::Debug for StageObject<'_> {
168178
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
169-
let this = self.0.read();
170179
f.debug_struct("StageObject")
171-
.field("ptr", &self.0.as_ptr())
172-
.field("display_object", &this.display_object)
180+
.field("ptr", &Gc::as_ptr(self.0))
181+
.field("display_object", &self.0.display_object)
173182
.finish()
174183
}
175184
}
176185

177186
impl<'gc> TObject<'gc> for StageObject<'gc> {
178187
fn raw_script_object(&self) -> ScriptObject<'gc> {
179-
self.0.read().base
188+
self.0.base
180189
}
181190

182191
fn get_local_stored(
@@ -186,11 +195,14 @@ impl<'gc> TObject<'gc> for StageObject<'gc> {
186195
is_slash_path: bool,
187196
) -> Option<Value<'gc>> {
188197
let name = name.into();
189-
let obj = self.0.read();
190198

191199
// Property search order for DisplayObjects:
192200
// 1) Actual properties on the underlying object
193-
if let Some(value) = obj.base.get_local_stored(name, activation, is_slash_path) {
201+
if let Some(value) = self
202+
.0
203+
.base
204+
.get_local_stored(name, activation, is_slash_path)
205+
{
194206
return Some(value);
195207
}
196208

@@ -203,7 +215,8 @@ impl<'gc> TObject<'gc> for StageObject<'gc> {
203215
}
204216

205217
// 3) Child display objects with the given instance name
206-
if let Some(child) = obj
218+
if let Some(child) = self
219+
.0
207220
.display_object
208221
.as_container()
209222
.and_then(|o| o.child_by_name(&name, activation.is_case_sensitive()))
@@ -228,7 +241,7 @@ impl<'gc> TObject<'gc> for StageObject<'gc> {
228241
.get_by_name(name)
229242
.copied()
230243
{
231-
return Some(property.get(activation, obj.display_object));
244+
return Some(property.get(activation, self.0.display_object));
232245
}
233246
}
234247

@@ -242,25 +255,28 @@ impl<'gc> TObject<'gc> for StageObject<'gc> {
242255
activation: &mut Activation<'_, 'gc>,
243256
this: Object<'gc>,
244257
) -> Result<(), Error<'gc>> {
245-
let obj = self.0.read();
246-
247258
// Check if a text field is bound to this property and update the text if so.
248259
let case_sensitive = activation.is_case_sensitive();
249-
for binding in obj.text_field_bindings.iter().filter(|binding| {
250-
if case_sensitive {
251-
binding.variable_name == name
252-
} else {
253-
binding.variable_name.eq_ignore_case(&name)
254-
}
255-
}) {
260+
for binding in self
261+
.0
262+
.text_field_bindings
263+
.borrow()
264+
.iter()
265+
.filter(|binding| {
266+
if case_sensitive {
267+
binding.variable_name == name
268+
} else {
269+
binding.variable_name.eq_ignore_case(&name)
270+
}
271+
})
272+
{
256273
binding
257274
.text_field
258275
.set_html_text(&value.coerce_to_string(activation)?, activation.context);
259276
}
260277

261-
let base = obj.base;
262-
let display_object = obj.display_object;
263-
drop(obj);
278+
let base = self.0.base;
279+
let display_object = self.0.display_object;
264280

265281
if base.has_own_property(activation, name) {
266282
// 1) Actual properties on the underlying object
@@ -286,14 +302,12 @@ impl<'gc> TObject<'gc> for StageObject<'gc> {
286302
this: Object<'gc>,
287303
) -> Result<Object<'gc>, Error<'gc>> {
288304
//TODO: Create a StageObject of some kind
289-
self.0.read().base.create_bare_object(activation, this)
305+
self.0.base.create_bare_object(activation, this)
290306
}
291307

292308
// Note that `hasOwnProperty` does NOT return true for child display objects.
293309
fn has_property(&self, activation: &mut Activation<'_, 'gc>, name: AvmString<'gc>) -> bool {
294-
let obj = self.0.read();
295-
296-
if !obj.display_object.avm1_removed() && obj.base.has_property(activation, name) {
310+
if !self.0.display_object.avm1_removed() && self.0.base.has_property(activation, name) {
297311
return true;
298312
}
299313

@@ -311,8 +325,9 @@ impl<'gc> TObject<'gc> for StageObject<'gc> {
311325

312326
let case_sensitive = activation.is_case_sensitive();
313327

314-
if !obj.display_object.avm1_removed()
315-
&& obj
328+
if !self.0.display_object.avm1_removed()
329+
&& self
330+
.0
316331
.display_object
317332
.as_container()
318333
.and_then(|o| o.child_by_name(&name, case_sensitive))
@@ -335,10 +350,9 @@ impl<'gc> TObject<'gc> for StageObject<'gc> {
335350
) -> Vec<AvmString<'gc>> {
336351
// Keys from the underlying object are listed first, followed by
337352
// child display objects in order from highest depth to lowest depth.
338-
let obj = self.0.read();
339-
let mut keys = obj.base.get_keys(activation, include_hidden);
353+
let mut keys = self.0.base.get_keys(activation, include_hidden);
340354

341-
if let Some(ctr) = obj.display_object.as_container() {
355+
if let Some(ctr) = self.0.display_object.as_container() {
342356
// Button/MovieClip children are included in key list.
343357
for child in ctr.iter_render_list().rev() {
344358
if child.as_interactive().is_some() {
@@ -356,11 +370,11 @@ impl<'gc> TObject<'gc> for StageObject<'gc> {
356370
}
357371

358372
fn as_display_object(&self) -> Option<DisplayObject<'gc>> {
359-
Some(self.0.read().display_object)
373+
Some(self.0.display_object)
360374
}
361375

362376
fn as_ptr(&self) -> *const ObjectPtr {
363-
self.0.read().base.as_ptr()
377+
self.0.base.as_ptr()
364378
}
365379
}
366380

‎core/src/avm1/object_reference.rs

+10-10
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ use crate::{
33
display_object::{DisplayObject, TDisplayObject, TDisplayObjectContainer},
44
string::{AvmString, WStr, WString},
55
};
6-
use gc_arena::lock::Lock;
7-
use gc_arena::{Collect, Gc, GcWeakCell, Mutation};
6+
use gc_arena::{lock::Lock, GcWeak};
7+
use gc_arena::{Collect, Gc, Mutation};
88
use ruffle_macros::istr;
99

1010
#[derive(Clone, Debug, Collect)]
@@ -63,7 +63,7 @@ struct MovieClipReferenceData<'gc> {
6363
/// A weak reference to the target stage object that `path` points to
6464
/// This is used for fast-path resvoling when possible, as well as for re-generating `path` (in the case the target object is renamed)
6565
/// If this is `None` then we have previously missed the cache, due to the target object being removed and re-created, causing us to fallback to the slow path resolution
66-
cached_stage_object: Lock<Option<GcWeakCell<'gc, StageObjectData<'gc>>>>,
66+
cached_stage_object: Lock<Option<GcWeak<'gc, StageObjectData<'gc>>>>,
6767
}
6868

6969
impl<'gc> MovieClipReference<'gc> {
@@ -116,16 +116,16 @@ impl<'gc> MovieClipReference<'gc> {
116116
/// Resolve this reference to an object
117117
/// First tuple param indificates if this path came from the cache or not
118118
pub fn resolve_reference(
119-
&self,
119+
self,
120120
activation: &mut Activation<'_, 'gc>,
121121
) -> Option<(bool, Object<'gc>, DisplayObject<'gc>)> {
122122
// Check if we have a cache we can use
123123
if let Some(cache) = self.0.cached_stage_object.get() {
124124
// Check if we can re-use the cached `DisplayObject`, if we can then take this fast path
125125
if let Some(mc) = cache.upgrade(activation.gc()) {
126126
// We have to fallback to manual path-walking if the object is removed
127-
if !mc.read().display_object.avm1_removed() {
128-
let display_object = mc.read().display_object;
127+
if !mc.display_object.avm1_removed() {
128+
let display_object = mc.display_object;
129129
let display_object = Self::process_swf5_references(activation, display_object)?;
130130

131131
// Note that there is a bug here but this *is* how it works in Flash:
@@ -180,13 +180,13 @@ impl<'gc> MovieClipReference<'gc> {
180180
}
181181

182182
/// Convert this reference to an `Object`
183-
pub fn coerce_to_object(&self, activation: &mut Activation<'_, 'gc>) -> Option<Object<'gc>> {
183+
pub fn coerce_to_object(self, activation: &mut Activation<'_, 'gc>) -> Option<Object<'gc>> {
184184
let (_, object, _) = self.resolve_reference(activation)?;
185185
Some(object)
186186
}
187187

188188
/// Convert this reference to a `String`
189-
pub fn coerce_to_string(&self, activation: &mut Activation<'_, 'gc>) -> AvmString<'gc> {
189+
pub fn coerce_to_string(self, activation: &mut Activation<'_, 'gc>) -> AvmString<'gc> {
190190
match self.resolve_reference(activation) {
191191
// Couldn't find the reference
192192
None => istr!(""),
@@ -199,7 +199,7 @@ impl<'gc> MovieClipReference<'gc> {
199199
}
200200

201201
/// Get the path used for this reference
202-
pub fn path(&self) -> &AvmString<'gc> {
203-
&self.0.path.full_path
202+
pub fn path(self) -> AvmString<'gc> {
203+
self.0.path.full_path
204204
}
205205
}

0 commit comments

Comments
 (0)
Please sign in to comment.