Skip to content

Commit

Permalink
Create objects for delegating components (#3303)
Browse files Browse the repository at this point in the history
### What

Builds on top of: #3302

One of the items raised by #3268
was wanting to extend a component type. But components previously had no
instantiation.

This PR creates Component objects that derive from their baseclass as
well as the extension class. These can be used anywhere the datatype
could be used instead.

Example with TextLogLevel:
```
>>> rr2.TextLog(body="Hello", level=rr2.cmp.TextLogLevel.WARN)
rr.TextLog(
  rerun.label<string>(
    ['Hello']
  )
  rerun.components.TextLogLevel<string>(
    ['WARN']
  )
  rerun.colorrgba<uint32>(
    []
  )
)

>>> rr2.TextLog(body="World", level=rr2.cmp.TextLogLevel("my custom level"))
rr.TextLog(
  rerun.label<string>(
    ['World']
  )
  rerun.components.TextLogLevel<string>(
    ['my custom level']
  )
  rerun.colorrgba<uint32>(
    []
  )
)
```

### Checklist
* [x] I have read and agree to [Contributor
Guide](https://github.com/rerun-io/rerun/blob/main/CONTRIBUTING.md) and
the [Code of
Conduct](https://github.com/rerun-io/rerun/blob/main/CODE_OF_CONDUCT.md)
* [x] I've included a screenshot or gif (if applicable)
* [x] I have tested [demo.rerun.io](https://demo.rerun.io/pr/3303) (if
applicable)

- [PR Build Summary](https://build.rerun.io/pr/3303)
- [Docs
preview](https://rerun.io/preview/177f58319e85da35d223c1fd0a109d35fbc7bd03/docs)
<!--DOCS-PREVIEW-->
- [Examples
preview](https://rerun.io/preview/177f58319e85da35d223c1fd0a109d35fbc7bd03/examples)
<!--EXAMPLES-PREVIEW-->
- [Recent benchmark results](https://ref.rerun.io/dev/bench/)
- [Wasm size tracking](https://ref.rerun.io/dev/sizes/)
  • Loading branch information
jleibs committed Sep 13, 2023
1 parent 6c052de commit 580101e
Show file tree
Hide file tree
Showing 28 changed files with 413 additions and 142 deletions.
2 changes: 1 addition & 1 deletion crates/re_types/source_hash.txt

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

177 changes: 103 additions & 74 deletions crates/re_types_builder/src/codegen/python.rs
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ impl PythonCodeGenerator {
let name = &obj.name;

if obj.is_delegating_component() {
vec![format!("{name}Array"), format!("{name}Type")]
vec![name.clone(), format!("{name}Array"), format!("{name}Type")]
} else {
vec![
format!("{name}"),
Expand Down Expand Up @@ -516,10 +516,11 @@ fn code_for_struct(

let mut code = String::new();

if *kind != ObjectKind::Component || obj.is_non_delegating_component() {
// field converters preprocessing pass — must be performed here because we must autogen
// converter function *before* the class
let mut field_converters: HashMap<String, String> = HashMap::new();
// field converters preprocessing pass — must be performed here because we must autogen
// converter function *before* the class
let mut field_converters: HashMap<String, String> = HashMap::new();

if !obj.is_delegating_component() {
for field in fields {
let (default_converter, converter_function) =
quote_field_converter_from_field(obj, objects, field);
Expand Down Expand Up @@ -552,88 +553,116 @@ fn code_for_struct(
};
field_converters.insert(field.fqname.clone(), converter);
}
}

// If the `ExtensionClass` has its own `__init__` then we need to pass the `init=False` argument
// to the `@define` decorator, to prevent it from generating its own `__init__`, which would
// take precedence over the `ExtensionClass`.
let old_init_override_name = format!("{}__init_override", obj.snake_case_name());
let init_define_arg = if ext_class.has_init || overrides.contains(&old_init_override_name) {
"init=False".to_owned()
} else {
String::new()
};
// If the `ExtensionClass` has its own `__init__` then we need to pass the `init=False` argument
// to the `@define` decorator, to prevent it from generating its own `__init__`, which would
// take precedence over the `ExtensionClass`.
let old_init_override_name = format!("{}__init_override", obj.snake_case_name());
let init_define_arg = if ext_class.has_init || overrides.contains(&old_init_override_name) {
"init=False".to_owned()
} else {
String::new()
};

let mut superclasses = vec![];
let mut superclasses = vec![];

if *kind == ObjectKind::Archetype {
superclasses.push("Archetype");
}
if *kind == ObjectKind::Archetype {
superclasses.push("Archetype".to_owned());
}

if ext_class.found {
superclasses.push(ext_class.name.as_str());
}
if ext_class.found {
superclasses.push(ext_class.name.clone());
}

let superclass_decl = if superclasses.is_empty() {
String::new()
} else {
format!("({})", superclasses.join(","))
};
// Delegating component inheritance comes after the `ExtensionClass`
// This way if a component needs to override `__init__` it still can.
if obj.is_delegating_component() {
superclasses.push(format!(
"datatypes.{}",
obj.delegate_datatype(objects).unwrap().name
));
}

let define_args = if *kind == ObjectKind::Archetype {
format!(
"str=False, repr=False{}{init_define_arg}",
if init_define_arg.is_empty() { "" } else { ", " }
)
} else {
init_define_arg
};
let superclass_decl = if superclasses.is_empty() {
String::new()
} else {
format!("({})", superclasses.join(","))
};

let define_args = if define_args.is_empty() {
define_args
} else {
format!("({define_args})")
};
let define_args = if *kind == ObjectKind::Archetype {
format!(
"str=False, repr=False{}{init_define_arg}",
if init_define_arg.is_empty() { "" } else { ", " }
)
} else {
init_define_arg
};

code.push_unindented_text(
format!(
r#"
@define{define_args}
let define_args = if define_args.is_empty() {
define_args
} else {
format!("({define_args})")
};

let define_decorator = if obj.is_delegating_component() {
String::new()
} else {
format!("@define{define_args}")
};

code.push_unindented_text(
format!(
r#"
{define_decorator}
class {name}{superclass_decl}:
"#
),
0,
);
),
0,
);

code.push_text(quote_doc_from_docs(docs), 0, 4);
code.push_text(quote_doc_from_docs(docs), 0, 4);

if ext_class.has_init {
code.push_text(
format!("# __init__ can be found in {}", ext_class.file_name),
2,
4,
);
} else if overrides.contains(&old_init_override_name) {
code.push_text(
"def __init__(self, *args, **kwargs): #type: ignore[no-untyped-def]",
1,
4,
);
code.push_text(
format!("{old_init_override_name}(self, *args, **kwargs)"),
2,
8,
);
} else {
code.push_text(
format!(
"# You can define your own __init__ function as a member of {} in {}",
ext_class.name, ext_class.file_name
),
2,
4,
);
}
if ext_class.has_init {
code.push_text(
format!("# __init__ can be found in {}", ext_class.file_name),
2,
4,
);
} else if overrides.contains(&old_init_override_name) {
code.push_text(
"def __init__(self, *args, **kwargs): #type: ignore[no-untyped-def]",
1,
4,
);
code.push_text(
format!("{old_init_override_name}(self, *args, **kwargs)"),
2,
8,
);
} else {
code.push_text(
format!(
"# You can define your own __init__ function as a member of {} in {}",
ext_class.name, ext_class.file_name
),
2,
4,
);
}

if obj.is_delegating_component() {
code.push_text(
format!(
"# Note: there are no fields here because {} delegates to datatypes.{}",
obj.name,
obj.delegate_datatype(objects).unwrap().name
),
1,
4,
);
code.push_text("pass", 2, 4);
} else {
// NOTE: We need to add required fields first, and then optional ones, otherwise mypy
// complains.
// TODO(ab, #2641): this is required because fields without default should appear before fields
Expand Down

1 comment on commit 580101e

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Performance Alert ⚠️

Possible performance regression was detected for benchmark 'Rust Benchmark'.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 1.25.

Benchmark suite Current: 580101e Previous: 6c052de Ratio
mono_points_arrow/decode_message_bundles 71179548 ns/iter (± 715465) 54762985 ns/iter (± 1074225) 1.30

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.