diff --git a/.editorconfig b/.editorconfig index fb43fdc2b..f57af0c2f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,613 +1,24 @@ +root = true + [*] charset = utf-8 -end_of_line = crlf -indent_size = 4 indent_style = space -insert_final_newline = false -max_line_length = 120 -tab_width = 4 -ij_continuation_indent_size = 8 -ij_formatter_off_tag = @formatter:off -ij_formatter_on_tag = @formatter:on -ij_formatter_tags_enabled = true -ij_smart_tabs = false -ij_visual_guides = none -ij_wrap_on_typing = false +indent_size = 4 +insert_final_newline = true [*.java] -ij_java_align_consecutive_assignments = false -ij_java_align_consecutive_variable_declarations = false -ij_java_align_group_field_declarations = false -ij_java_align_multiline_annotation_parameters = false -ij_java_align_multiline_array_initializer_expression = false -ij_java_align_multiline_assignment = false -ij_java_align_multiline_binary_operation = true -ij_java_align_multiline_chained_methods = false -ij_java_align_multiline_extends_list = false -ij_java_align_multiline_for = true -ij_java_align_multiline_method_parentheses = false -ij_java_align_multiline_parameters = true -ij_java_align_multiline_parameters_in_calls = false -ij_java_align_multiline_parenthesized_expression = false -ij_java_align_multiline_records = true -ij_java_align_multiline_resources = true -ij_java_align_multiline_ternary_operation = false -ij_java_align_multiline_text_blocks = false -ij_java_align_multiline_throws_list = false -ij_java_align_subsequent_simple_methods = false -ij_java_align_throws_keyword = false -ij_java_annotation_parameter_wrap = off -ij_java_array_initializer_new_line_after_left_brace = false -ij_java_array_initializer_right_brace_on_new_line = false -ij_java_array_initializer_wrap = off -ij_java_assert_statement_colon_on_next_line = true -ij_java_assert_statement_wrap = off -ij_java_assignment_wrap = off -ij_java_binary_operation_sign_on_next_line = false -ij_java_binary_operation_wrap = off -ij_java_blank_lines_after_anonymous_class_header = 0 -ij_java_blank_lines_after_class_header = 0 -ij_java_blank_lines_after_imports = 1 -ij_java_blank_lines_after_package = 1 -ij_java_blank_lines_around_class = 1 -ij_java_blank_lines_around_field = 0 -ij_java_blank_lines_around_field_in_interface = 0 -ij_java_blank_lines_around_initializer = 1 -ij_java_blank_lines_around_method = 1 -ij_java_blank_lines_around_method_in_interface = 1 -ij_java_blank_lines_before_class_end = 0 -ij_java_blank_lines_before_imports = 1 -ij_java_blank_lines_before_method_body = 0 -ij_java_blank_lines_before_package = 0 -ij_java_block_brace_style = end_of_line -ij_java_block_comment_at_first_column = true -ij_java_call_parameters_new_line_after_left_paren = false -ij_java_call_parameters_right_paren_on_new_line = false -ij_java_call_parameters_wrap = off -ij_java_case_statement_on_separate_line = true -ij_java_catch_on_new_line = false -ij_java_class_annotation_wrap = split_into_lines -ij_java_class_brace_style = end_of_line -ij_java_class_count_to_use_import_on_demand = 5 -ij_java_class_names_in_javadoc = 1 -ij_java_do_not_indent_top_level_class_members = false -ij_java_do_not_wrap_after_single_annotation = true -ij_java_do_while_brace_force = never -ij_java_doc_add_blank_line_after_description = true -ij_java_doc_add_blank_line_after_param_comments = false -ij_java_doc_add_blank_line_after_return = false -ij_java_doc_add_p_tag_on_empty_lines = true -ij_java_doc_align_exception_comments = true -ij_java_doc_align_param_comments = true -ij_java_doc_do_not_wrap_if_one_line = false -ij_java_doc_enable_formatting = true -ij_java_doc_enable_leading_asterisks = true -ij_java_doc_indent_on_continuation = false -ij_java_doc_keep_empty_lines = true -ij_java_doc_keep_empty_parameter_tag = true -ij_java_doc_keep_empty_return_tag = true -ij_java_doc_keep_empty_throws_tag = true -ij_java_doc_keep_invalid_tags = true -ij_java_doc_param_description_on_new_line = false -ij_java_doc_preserve_line_breaks = false -ij_java_doc_use_throws_not_exception_tag = true -ij_java_else_on_new_line = false -ij_java_enum_constants_wrap = off -ij_java_extends_keyword_wrap = off -ij_java_extends_list_wrap = off -ij_java_field_annotation_wrap = normal -ij_java_finally_on_new_line = false -ij_java_for_brace_force = never -ij_java_for_statement_new_line_after_left_paren = false -ij_java_for_statement_right_paren_on_new_line = false -ij_java_for_statement_wrap = off +trim_trailing_whitespace = true +max_line_length = 120 ij_java_generate_final_locals = true ij_java_generate_final_parameters = true -ij_java_if_brace_force = never -ij_java_imports_layout = *, |, javax.**, java.**, |, $* -ij_java_indent_case_from_switch = true -ij_java_insert_inner_class_imports = false -ij_java_insert_override_annotation = true -ij_java_keep_blank_lines_before_right_brace = 2 -ij_java_keep_blank_lines_between_package_declaration_and_header = 2 -ij_java_keep_blank_lines_in_code = 2 -ij_java_keep_blank_lines_in_declarations = 2 -ij_java_keep_control_statement_in_one_line = true -ij_java_keep_first_column_comment = true -ij_java_keep_indents_on_empty_lines = false -ij_java_keep_line_breaks = true -ij_java_keep_multiple_expressions_in_one_line = false -ij_java_keep_simple_blocks_in_one_line = false -ij_java_keep_simple_classes_in_one_line = false -ij_java_keep_simple_lambdas_in_one_line = false -ij_java_keep_simple_methods_in_one_line = false -ij_java_label_indent_absolute = false -ij_java_label_indent_size = 0 -ij_java_lambda_brace_style = end_of_line -ij_java_layout_static_imports_separately = true -ij_java_line_comment_add_space = false -ij_java_line_comment_at_first_column = true -ij_java_method_annotation_wrap = split_into_lines -ij_java_method_brace_style = end_of_line -ij_java_method_call_chain_wrap = off -ij_java_method_parameters_new_line_after_left_paren = false -ij_java_method_parameters_right_paren_on_new_line = false -ij_java_method_parameters_wrap = off -ij_java_modifier_list_wrap = false -ij_java_names_count_to_use_import_on_demand = 3 -ij_java_new_line_after_lparen_in_record_header = false -ij_java_packages_to_use_import_on_demand = java.awt.*, javax.swing.* -ij_java_parameter_annotation_wrap = off -ij_java_parentheses_expression_new_line_after_left_paren = false -ij_java_parentheses_expression_right_paren_on_new_line = false -ij_java_place_assignment_sign_on_next_line = false -ij_java_prefer_longer_names = true -ij_java_prefer_parameters_wrap = false -ij_java_record_components_wrap = normal -ij_java_repeat_synchronized = true -ij_java_replace_instanceof_and_cast = false -ij_java_replace_null_check = true -ij_java_replace_sum_lambda_with_method_ref = true -ij_java_resource_list_new_line_after_left_paren = false -ij_java_resource_list_right_paren_on_new_line = false -ij_java_resource_list_wrap = off -ij_java_rparen_on_new_line_in_record_header = false -ij_java_space_after_closing_angle_bracket_in_type_argument = false -ij_java_space_after_colon = true -ij_java_space_after_comma = true -ij_java_space_after_comma_in_type_arguments = true -ij_java_space_after_for_semicolon = true -ij_java_space_after_quest = true -ij_java_space_after_type_cast = true -ij_java_space_before_annotation_array_initializer_left_brace = false -ij_java_space_before_annotation_parameter_list = false -ij_java_space_before_array_initializer_left_brace = false -ij_java_space_before_catch_keyword = true -ij_java_space_before_catch_left_brace = true -ij_java_space_before_catch_parentheses = true -ij_java_space_before_class_left_brace = true -ij_java_space_before_colon = true -ij_java_space_before_colon_in_foreach = true -ij_java_space_before_comma = false -ij_java_space_before_do_left_brace = true -ij_java_space_before_else_keyword = true -ij_java_space_before_else_left_brace = true -ij_java_space_before_finally_keyword = true -ij_java_space_before_finally_left_brace = true -ij_java_space_before_for_left_brace = true -ij_java_space_before_for_parentheses = true -ij_java_space_before_for_semicolon = false -ij_java_space_before_if_left_brace = true -ij_java_space_before_if_parentheses = true -ij_java_space_before_method_call_parentheses = false -ij_java_space_before_method_left_brace = true -ij_java_space_before_method_parentheses = false -ij_java_space_before_opening_angle_bracket_in_type_parameter = false -ij_java_space_before_quest = true -ij_java_space_before_switch_left_brace = true -ij_java_space_before_switch_parentheses = true -ij_java_space_before_synchronized_left_brace = true -ij_java_space_before_synchronized_parentheses = true -ij_java_space_before_try_left_brace = true -ij_java_space_before_try_parentheses = true -ij_java_space_before_type_parameter_list = false -ij_java_space_before_while_keyword = true -ij_java_space_before_while_left_brace = true -ij_java_space_before_while_parentheses = true -ij_java_space_inside_one_line_enum_braces = false -ij_java_space_within_empty_array_initializer_braces = false -ij_java_space_within_empty_method_call_parentheses = false -ij_java_space_within_empty_method_parentheses = false -ij_java_spaces_around_additive_operators = true -ij_java_spaces_around_assignment_operators = true -ij_java_spaces_around_bitwise_operators = true -ij_java_spaces_around_equality_operators = true -ij_java_spaces_around_lambda_arrow = true -ij_java_spaces_around_logical_operators = true -ij_java_spaces_around_method_ref_dbl_colon = false -ij_java_spaces_around_multiplicative_operators = true -ij_java_spaces_around_relational_operators = true -ij_java_spaces_around_shift_operators = true -ij_java_spaces_around_type_bounds_in_type_parameters = true -ij_java_spaces_around_unary_operator = false -ij_java_spaces_within_angle_brackets = false -ij_java_spaces_within_annotation_parentheses = false -ij_java_spaces_within_array_initializer_braces = false -ij_java_spaces_within_braces = false -ij_java_spaces_within_brackets = false -ij_java_spaces_within_cast_parentheses = false -ij_java_spaces_within_catch_parentheses = false -ij_java_spaces_within_for_parentheses = false -ij_java_spaces_within_if_parentheses = false -ij_java_spaces_within_method_call_parentheses = false -ij_java_spaces_within_method_parentheses = false -ij_java_spaces_within_parentheses = false -ij_java_spaces_within_record_header = false -ij_java_spaces_within_switch_parentheses = false -ij_java_spaces_within_synchronized_parentheses = false -ij_java_spaces_within_try_parentheses = false -ij_java_spaces_within_while_parentheses = false -ij_java_special_else_if_treatment = true -ij_java_subclass_name_suffix = Impl -ij_java_ternary_operation_signs_on_next_line = false -ij_java_ternary_operation_wrap = off -ij_java_test_name_suffix = Test -ij_java_throws_keyword_wrap = off -ij_java_throws_list_wrap = off -ij_java_use_external_annotations = false -ij_java_use_fq_class_names = false -ij_java_use_relative_indents = false -ij_java_use_single_class_imports = true -ij_java_variable_annotation_wrap = off -ij_java_visibility = public -ij_java_while_brace_force = never -ij_java_while_on_new_line = false -ij_java_wrap_comments = false -ij_java_wrap_first_method_in_call_chain = false -ij_java_wrap_long_lines = false -[*.nbtt] -max_line_length = 150 -ij_continuation_indent_size = 4 -ij_nbtt_keep_indents_on_empty_lines = false -ij_nbtt_space_after_colon = true -ij_nbtt_space_after_comma = true -ij_nbtt_space_before_colon = true -ij_nbtt_space_before_comma = false -ij_nbtt_spaces_within_brackets = false -ij_nbtt_spaces_within_parentheses = false +[*.yml] +indent_size = 2 +tab_width = 2 [*.properties] -ij_properties_align_group_field_declarations = false -ij_properties_keep_blank_lines = false -ij_properties_key_value_delimiter = equals -ij_properties_spaces_around_key_value_delimiter = false - -[.editorconfig] -ij_editorconfig_align_group_field_declarations = false -ij_editorconfig_space_after_colon = false -ij_editorconfig_space_after_comma = true -ij_editorconfig_space_before_colon = false -ij_editorconfig_space_before_comma = false -ij_editorconfig_spaces_around_assignment_operators = true - -[{*.ant, *.fxml, *.jhm, *.jnlp, *.jrxml, *.jspx, *.pom, *.rng, *.tagx, *.tld, *.wsdl, *.xml, *.xsd, *.xsl, *.xslt, *.xul}] -ij_xml_align_attributes = true -ij_xml_align_text = false -ij_xml_attribute_wrap = normal -ij_xml_block_comment_at_first_column = true -ij_xml_keep_blank_lines = 2 -ij_xml_keep_indents_on_empty_lines = false -ij_xml_keep_line_breaks = true -ij_xml_keep_line_breaks_in_text = true -ij_xml_keep_whitespaces = false -ij_xml_keep_whitespaces_around_cdata = preserve -ij_xml_keep_whitespaces_inside_cdata = false -ij_xml_line_comment_at_first_column = true -ij_xml_space_after_tag_name = false -ij_xml_space_around_equals_in_attribute = false -ij_xml_space_inside_empty_tag = false -ij_xml_text_wrap = normal +ij_properties_keep_blank_lines = true -[{*.comp, *.frag, *.fsh, *.geom, *.glsl, *.shader, *.tesc, *.tese, *.vert, *.vsh}] -ij_glsl_keep_indents_on_empty_lines = false - -[{*.gant, *.gradle, *.groovy, *.gy}] -ij_groovy_align_group_field_declarations = false -ij_groovy_align_multiline_array_initializer_expression = false -ij_groovy_align_multiline_assignment = false -ij_groovy_align_multiline_binary_operation = false -ij_groovy_align_multiline_chained_methods = false -ij_groovy_align_multiline_extends_list = false -ij_groovy_align_multiline_for = true -ij_groovy_align_multiline_list_or_map = true -ij_groovy_align_multiline_method_parentheses = false -ij_groovy_align_multiline_parameters = true -ij_groovy_align_multiline_parameters_in_calls = false -ij_groovy_align_multiline_resources = true -ij_groovy_align_multiline_ternary_operation = false -ij_groovy_align_multiline_throws_list = false -ij_groovy_align_named_args_in_map = true -ij_groovy_align_throws_keyword = false -ij_groovy_array_initializer_new_line_after_left_brace = false -ij_groovy_array_initializer_right_brace_on_new_line = false -ij_groovy_array_initializer_wrap = off -ij_groovy_assert_statement_wrap = off -ij_groovy_assignment_wrap = off -ij_groovy_binary_operation_wrap = off -ij_groovy_blank_lines_after_class_header = 0 -ij_groovy_blank_lines_after_imports = 1 -ij_groovy_blank_lines_after_package = 1 -ij_groovy_blank_lines_around_class = 1 -ij_groovy_blank_lines_around_field = 0 -ij_groovy_blank_lines_around_field_in_interface = 0 -ij_groovy_blank_lines_around_method = 1 -ij_groovy_blank_lines_around_method_in_interface = 1 -ij_groovy_blank_lines_before_imports = 1 -ij_groovy_blank_lines_before_method_body = 0 -ij_groovy_blank_lines_before_package = 0 -ij_groovy_block_brace_style = end_of_line -ij_groovy_block_comment_at_first_column = true -ij_groovy_call_parameters_new_line_after_left_paren = false -ij_groovy_call_parameters_right_paren_on_new_line = false -ij_groovy_call_parameters_wrap = off -ij_groovy_catch_on_new_line = false -ij_groovy_class_annotation_wrap = split_into_lines -ij_groovy_class_brace_style = end_of_line -ij_groovy_class_count_to_use_import_on_demand = 5 -ij_groovy_do_while_brace_force = never -ij_groovy_else_on_new_line = false -ij_groovy_enum_constants_wrap = off -ij_groovy_extends_keyword_wrap = off -ij_groovy_extends_list_wrap = off -ij_groovy_field_annotation_wrap = split_into_lines -ij_groovy_finally_on_new_line = false -ij_groovy_for_brace_force = never -ij_groovy_for_statement_new_line_after_left_paren = false -ij_groovy_for_statement_right_paren_on_new_line = false -ij_groovy_for_statement_wrap = off -ij_groovy_if_brace_force = never -ij_groovy_import_annotation_wrap = 2 -ij_groovy_imports_layout = *, |, javax.**, java.**, |, $* -ij_groovy_indent_case_from_switch = true -ij_groovy_indent_label_blocks = true -ij_groovy_insert_inner_class_imports = false -ij_groovy_keep_blank_lines_before_right_brace = 2 -ij_groovy_keep_blank_lines_in_code = 2 -ij_groovy_keep_blank_lines_in_declarations = 2 -ij_groovy_keep_control_statement_in_one_line = true -ij_groovy_keep_first_column_comment = true -ij_groovy_keep_indents_on_empty_lines = false -ij_groovy_keep_line_breaks = true -ij_groovy_keep_multiple_expressions_in_one_line = false -ij_groovy_keep_simple_blocks_in_one_line = false -ij_groovy_keep_simple_classes_in_one_line = true -ij_groovy_keep_simple_lambdas_in_one_line = true -ij_groovy_keep_simple_methods_in_one_line = true -ij_groovy_label_indent_absolute = false -ij_groovy_label_indent_size = 0 -ij_groovy_lambda_brace_style = end_of_line -ij_groovy_layout_static_imports_separately = true -ij_groovy_line_comment_add_space = false -ij_groovy_line_comment_at_first_column = true -ij_groovy_method_annotation_wrap = split_into_lines -ij_groovy_method_brace_style = end_of_line -ij_groovy_method_call_chain_wrap = off -ij_groovy_method_parameters_new_line_after_left_paren = false -ij_groovy_method_parameters_right_paren_on_new_line = false -ij_groovy_method_parameters_wrap = off -ij_groovy_modifier_list_wrap = false -ij_groovy_names_count_to_use_import_on_demand = 3 -ij_groovy_parameter_annotation_wrap = off -ij_groovy_parentheses_expression_new_line_after_left_paren = false -ij_groovy_parentheses_expression_right_paren_on_new_line = false -ij_groovy_prefer_parameters_wrap = false -ij_groovy_resource_list_new_line_after_left_paren = false -ij_groovy_resource_list_right_paren_on_new_line = false -ij_groovy_resource_list_wrap = off -ij_groovy_space_after_assert_separator = true -ij_groovy_space_after_colon = true -ij_groovy_space_after_comma = true -ij_groovy_space_after_comma_in_type_arguments = true -ij_groovy_space_after_for_semicolon = true -ij_groovy_space_after_quest = true -ij_groovy_space_after_type_cast = true -ij_groovy_space_before_annotation_parameter_list = false -ij_groovy_space_before_array_initializer_left_brace = false -ij_groovy_space_before_assert_separator = false -ij_groovy_space_before_catch_keyword = true -ij_groovy_space_before_catch_left_brace = true -ij_groovy_space_before_catch_parentheses = true -ij_groovy_space_before_class_left_brace = true -ij_groovy_space_before_closure_left_brace = true -ij_groovy_space_before_colon = true -ij_groovy_space_before_comma = false -ij_groovy_space_before_do_left_brace = true -ij_groovy_space_before_else_keyword = true -ij_groovy_space_before_else_left_brace = true -ij_groovy_space_before_finally_keyword = true -ij_groovy_space_before_finally_left_brace = true -ij_groovy_space_before_for_left_brace = true -ij_groovy_space_before_for_parentheses = true -ij_groovy_space_before_for_semicolon = false -ij_groovy_space_before_if_left_brace = true -ij_groovy_space_before_if_parentheses = true -ij_groovy_space_before_method_call_parentheses = false -ij_groovy_space_before_method_left_brace = true -ij_groovy_space_before_method_parentheses = false -ij_groovy_space_before_quest = true -ij_groovy_space_before_switch_left_brace = true -ij_groovy_space_before_switch_parentheses = true -ij_groovy_space_before_synchronized_left_brace = true -ij_groovy_space_before_synchronized_parentheses = true -ij_groovy_space_before_try_left_brace = true -ij_groovy_space_before_try_parentheses = true -ij_groovy_space_before_while_keyword = true -ij_groovy_space_before_while_left_brace = true -ij_groovy_space_before_while_parentheses = true -ij_groovy_space_in_named_argument = true -ij_groovy_space_in_named_argument_before_colon = false -ij_groovy_space_within_empty_array_initializer_braces = false -ij_groovy_space_within_empty_method_call_parentheses = false -ij_groovy_spaces_around_additive_operators = true -ij_groovy_spaces_around_assignment_operators = true -ij_groovy_spaces_around_bitwise_operators = true -ij_groovy_spaces_around_equality_operators = true -ij_groovy_spaces_around_lambda_arrow = true -ij_groovy_spaces_around_logical_operators = true -ij_groovy_spaces_around_multiplicative_operators = true -ij_groovy_spaces_around_regex_operators = true -ij_groovy_spaces_around_relational_operators = true -ij_groovy_spaces_around_shift_operators = true -ij_groovy_spaces_within_annotation_parentheses = false -ij_groovy_spaces_within_array_initializer_braces = false -ij_groovy_spaces_within_braces = true -ij_groovy_spaces_within_brackets = false -ij_groovy_spaces_within_cast_parentheses = false -ij_groovy_spaces_within_catch_parentheses = false -ij_groovy_spaces_within_for_parentheses = false -ij_groovy_spaces_within_gstring_injection_braces = false -ij_groovy_spaces_within_if_parentheses = false -ij_groovy_spaces_within_list_or_map = false -ij_groovy_spaces_within_method_call_parentheses = false -ij_groovy_spaces_within_method_parentheses = false -ij_groovy_spaces_within_parentheses = false -ij_groovy_spaces_within_switch_parentheses = false -ij_groovy_spaces_within_synchronized_parentheses = false -ij_groovy_spaces_within_try_parentheses = false -ij_groovy_spaces_within_tuple_expression = false -ij_groovy_spaces_within_while_parentheses = false -ij_groovy_special_else_if_treatment = true -ij_groovy_ternary_operation_wrap = off -ij_groovy_throws_keyword_wrap = off -ij_groovy_throws_list_wrap = off -ij_groovy_use_flying_geese_braces = false -ij_groovy_use_fq_class_names = false -ij_groovy_use_fq_class_names_in_javadoc = true -ij_groovy_use_relative_indents = false -ij_groovy_use_single_class_imports = true -ij_groovy_variable_annotation_wrap = off -ij_groovy_while_brace_force = never -ij_groovy_while_on_new_line = false -ij_groovy_wrap_long_lines = false - -[{*.gradle.kts, *.kt, *.kts, *.main.kts}] -ij_kotlin_align_in_columns_case_branch = false -ij_kotlin_align_multiline_binary_operation = false -ij_kotlin_align_multiline_extends_list = false -ij_kotlin_align_multiline_method_parentheses = false -ij_kotlin_align_multiline_parameters = true -ij_kotlin_align_multiline_parameters_in_calls = false -ij_kotlin_allow_trailing_comma = false -ij_kotlin_allow_trailing_comma_on_call_site = false -ij_kotlin_assignment_wrap = off -ij_kotlin_blank_lines_after_class_header = 0 -ij_kotlin_blank_lines_around_block_when_branches = 0 -ij_kotlin_blank_lines_before_declaration_with_comment_or_annotation_on_separate_line = 1 -ij_kotlin_block_comment_at_first_column = true -ij_kotlin_call_parameters_new_line_after_left_paren = false -ij_kotlin_call_parameters_right_paren_on_new_line = false -ij_kotlin_call_parameters_wrap = off -ij_kotlin_catch_on_new_line = false -ij_kotlin_class_annotation_wrap = split_into_lines -ij_kotlin_continuation_indent_for_chained_calls = true -ij_kotlin_continuation_indent_for_expression_bodies = true -ij_kotlin_continuation_indent_in_argument_lists = true -ij_kotlin_continuation_indent_in_elvis = true -ij_kotlin_continuation_indent_in_if_conditions = true -ij_kotlin_continuation_indent_in_parameter_lists = true -ij_kotlin_continuation_indent_in_supertype_lists = true -ij_kotlin_else_on_new_line = false -ij_kotlin_enum_constants_wrap = off -ij_kotlin_extends_list_wrap = off -ij_kotlin_field_annotation_wrap = split_into_lines -ij_kotlin_finally_on_new_line = false -ij_kotlin_if_rparen_on_new_line = false -ij_kotlin_import_nested_classes = false -ij_kotlin_imports_layout = *, java.**, javax.**, kotlin.**, ^ -ij_kotlin_insert_whitespaces_in_simple_one_line_method = true -ij_kotlin_keep_blank_lines_before_right_brace = 2 -ij_kotlin_keep_blank_lines_in_code = 2 -ij_kotlin_keep_blank_lines_in_declarations = 2 -ij_kotlin_keep_first_column_comment = true -ij_kotlin_keep_indents_on_empty_lines = false -ij_kotlin_keep_line_breaks = true -ij_kotlin_lbrace_on_next_line = false -ij_kotlin_line_comment_add_space = false -ij_kotlin_line_comment_at_first_column = true -ij_kotlin_method_annotation_wrap = split_into_lines -ij_kotlin_method_call_chain_wrap = off -ij_kotlin_method_parameters_new_line_after_left_paren = false -ij_kotlin_method_parameters_right_paren_on_new_line = false -ij_kotlin_method_parameters_wrap = off -ij_kotlin_name_count_to_use_star_import = 5 -ij_kotlin_name_count_to_use_star_import_for_members = 3 -ij_kotlin_packages_to_use_import_on_demand = java.util.*, kotlinx.android.synthetic.**, io.ktor.** -ij_kotlin_parameter_annotation_wrap = off -ij_kotlin_space_after_comma = true -ij_kotlin_space_after_extend_colon = true -ij_kotlin_space_after_type_colon = true -ij_kotlin_space_before_catch_parentheses = true -ij_kotlin_space_before_comma = false -ij_kotlin_space_before_extend_colon = true -ij_kotlin_space_before_for_parentheses = true -ij_kotlin_space_before_if_parentheses = true -ij_kotlin_space_before_lambda_arrow = true -ij_kotlin_space_before_type_colon = false -ij_kotlin_space_before_when_parentheses = true -ij_kotlin_space_before_while_parentheses = true -ij_kotlin_spaces_around_additive_operators = true -ij_kotlin_spaces_around_assignment_operators = true -ij_kotlin_spaces_around_equality_operators = true -ij_kotlin_spaces_around_function_type_arrow = true -ij_kotlin_spaces_around_logical_operators = true -ij_kotlin_spaces_around_multiplicative_operators = true -ij_kotlin_spaces_around_range = false -ij_kotlin_spaces_around_relational_operators = true -ij_kotlin_spaces_around_unary_operator = false -ij_kotlin_spaces_around_when_arrow = true -ij_kotlin_variable_annotation_wrap = off -ij_kotlin_while_on_new_line = false -ij_kotlin_wrap_elvis_expressions = 1 -ij_kotlin_wrap_expression_body_functions = 0 -ij_kotlin_wrap_first_method_in_call_chain = false - -[{*.har, *.json, *.mcmeta, mcmod.info, pack.mcmeta}] +[*.json] indent_size = 2 -ij_json_keep_blank_lines_in_code = 1 -ij_json_keep_indents_on_empty_lines = false -ij_json_keep_line_breaks = true -ij_json_space_after_colon = true -ij_json_space_after_comma = true -ij_json_space_before_colon = true -ij_json_space_before_comma = false -ij_json_spaces_within_braces = true -ij_json_spaces_within_brackets = true -ij_json_wrap_long_lines = false - -[{*.htm, *.html, *.sht, *.shtm, *.shtml}] -ij_html_add_new_line_before_tags = body, div, p, form, h1, h2, h3 -ij_html_align_attributes = true -ij_html_align_text = false -ij_html_attribute_wrap = normal -ij_html_block_comment_at_first_column = true -ij_html_do_not_align_children_of_min_lines = 0 -ij_html_do_not_break_if_inline_tags = title, h1, h2, h3, h4, h5, h6, p -ij_html_do_not_indent_children_of_tags = html, body, thead, tbody, tfoot -ij_html_enforce_quotes = false -ij_html_inline_tags = a, abbr, acronym, b, basefont, bdo, big, br, cite, cite, code, dfn, em, font, i, img, input, kbd, label, q, s, samp, select, small, span, strike, strong, sub, sup, textarea, tt, u, var -ij_html_keep_blank_lines = 2 -ij_html_keep_indents_on_empty_lines = false -ij_html_keep_line_breaks = true -ij_html_keep_line_breaks_in_text = true -ij_html_keep_whitespaces = false -ij_html_keep_whitespaces_inside = span, pre, textarea -ij_html_line_comment_at_first_column = true -ij_html_new_line_after_last_attribute = never -ij_html_new_line_before_first_attribute = never -ij_html_quote_style = double -ij_html_remove_new_line_before_tags = br -ij_html_space_after_tag_name = false -ij_html_space_around_equality_in_attribute = false -ij_html_space_inside_empty_tag = false -ij_html_text_wrap = normal -ij_html_uniform_ident = false - -[{*.markdown, *.md}] -ij_markdown_force_one_space_after_blockquote_symbol = true -ij_markdown_force_one_space_after_header_symbol = true -ij_markdown_force_one_space_after_list_bullet = true -ij_markdown_force_one_space_between_words = true -ij_markdown_keep_indents_on_empty_lines = false -ij_markdown_max_lines_around_block_elements = 1 -ij_markdown_max_lines_around_header = 1 -ij_markdown_max_lines_between_paragraphs = 1 -ij_markdown_min_lines_around_block_elements = 1 -ij_markdown_min_lines_around_header = 1 -ij_markdown_min_lines_between_paragraphs = 1 - -[{*.toml, Cargo.lock, Gopkg.lock, Pipfile}] -ij_toml_keep_indents_on_empty_lines = false - -[.lua] -end_of_line = lf +tab_width = 2 diff --git a/.gitattributes b/.gitattributes index e070e217d..48765538c 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5,4 +5,4 @@ src/generated/**/.cache/cache text eol=lf src/generated/**/*.json text eol=lf # Files provided to VM. -src/main/resources/data/oc2/file_systems/**/*.lua text eol=lf +src/main/scripts/**/*.lua text eol=lf diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2ebbc5ae3..f987873a9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,32 +1,37 @@ name: build -on: [pull_request, push] +on: + pull_request: + push: + branches: [ '**' ] jobs: build: strategy: matrix: - # Use these Java versions - java: [ 1.8 ] - # and run on both Linux and Windows - os: [ubuntu-latest, windows-latest] + java: [ 17 ] + os: [ ubuntu-latest, windows-latest ] runs-on: ${{ matrix.os }} + steps: - - uses: actions/checkout@v2 - - name: Set up JDK ${{ matrix.java }} - uses: actions/setup-java@v1 - with: - java-version: ${{ matrix.java }} - - name: Grant execute permission for gradlew - run: chmod +x gradlew - - uses: actions/cache@v2 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }} - restore-keys: ${{ runner.os }}-gradle- - - name: Build with Gradle - run: ./gradlew build - env: - BUILD_NUMBER: ${{ github.run_number }} + - uses: actions/checkout@v2 + - name: Set up JDK ${{ matrix.java }} + uses: actions/setup-java@v1 + with: + java-version: ${{ matrix.java }} + - name: Grant execute permission for gradlew + run: chmod +x gradlew + - name: Validate Gradle wrapper + uses: gradle/wrapper-validation-action@v1 + - uses: actions/cache@v2 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }} + restore-keys: ${{ runner.os }}-gradle- + - name: Build with Gradle + run: ./gradlew build + env: + GPR_USER: ${{ secrets.GPR_USER }} + GPR_KEY: ${{ secrets.GPR_KEY }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 000000000..0de6a7175 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,101 @@ +name: publish + +on: + release: + types: [published] + +jobs: + publish-github: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Extract Version from Tag + uses: rishabhgupta/split-by@v1 + id: split_tag + with: + string: ${{ github.event.release.tag_name }} + split-by: '/' + + - name: Set up JDK + uses: actions/setup-java@v1 + with: + java-version: 17 + - name: Grant execute permission for gradlew + run: chmod +x gradlew + - name: Validate Gradle wrapper + uses: gradle/wrapper-validation-action@v1 + - uses: actions/cache@v2 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ubuntu-latest-gradle-${{ hashFiles('**/*.gradle*') }} + restore-keys: ubuntu-latest-gradle- + - name: Build with Gradle + run: ./gradlew -Psemver='${{ steps.split_tag.outputs._1 }}' build + env: + GPR_USER: ${{ secrets.GPR_USER }} + GPR_KEY: ${{ secrets.GPR_KEY }} + + - name: Add Artifacts to Github Release + uses: alexellis/upload-assets@0.3.0 + env: + GITHUB_TOKEN: ${{ github.token }} + with: + asset_paths: '["./build/libs/*.jar"]' + + - name: Publish to Github Packages + run: gradle -Psemver='${{ steps.split_tag.outputs._1 }}' publish + env: + GPR_USER: ${{ secrets.GPR_USER }} + GPR_KEY: ${{ secrets.GPR_KEY }} + GITHUB_MAVEN_URL: 'https://maven.pkg.github.com/${{ github.repository }}' + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + publish-curse: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Extract Version from Tag + uses: rishabhgupta/split-by@v1 + id: split_tag + with: + string: ${{ github.event.release.tag_name }} + split-by: '/' + + - name: Set up JDK + uses: actions/setup-java@v1 + with: + java-version: 17 + - name: Grant execute permission for gradlew + run: chmod +x gradlew + - name: Validate Gradle wrapper + uses: gradle/wrapper-validation-action@v1 + - uses: actions/cache@v2 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ubuntu-latest-gradle-${{ hashFiles('**/*.gradle*') }} + restore-keys: ubuntu-latest-gradle- + + # Set Curseforge release type based on pre-release flag. + - name: Set release type to 'release' + run: | + echo "CURSEFORGE_RELEASE_TYPE=release" >> $GITHUB_ENV + if: github.event.release.prerelease == false + - name: Set release type to 'alpha' + run: | + echo "CURSEFORGE_RELEASE_TYPE=alpha" >> $GITHUB_ENV + if: github.event.release.prerelease == true + + - name: Publish to Curseforge + run: ./gradlew -Psemver='${{ steps.split_tag.outputs._1 }}' curseforge + env: + GPR_USER: ${{ secrets.GPR_USER }} + GPR_KEY: ${{ secrets.GPR_KEY }} + CURSEFORGE_API_KEY: ${{ secrets.CURSEFORGE_API_KEY }} + CURSEFORGE_RELEASE_TYPE: ${{ env.CURSEFORGE_RELEASE_TYPE }} + CHANGELOG: ${{ github.event.release.body }} diff --git a/.gitignore b/.gitignore index 1867a942b..b3f478f8b 100644 --- a/.gitignore +++ b/.gitignore @@ -31,4 +31,7 @@ forge*changelog.txt /src/generated/ #vscode -.vscode \ No newline at end of file +.vscode + +# Don't ignore bin from scripts +!/src/main/scripts/bin/ diff --git a/LICENSE-JCODEC b/LICENSE-JCODEC new file mode 100644 index 000000000..85cf456af --- /dev/null +++ b/LICENSE-JCODEC @@ -0,0 +1,21 @@ +Copyright 2008-2019 JCodecProject + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. Redistributions in binary form +must reproduce the above copyright notice, this list of conditions and the +following disclaimer in the documentation and/or other materials provided with +the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md index 90ed4b90a..297d0a8e1 100644 --- a/README.md +++ b/README.md @@ -4,15 +4,24 @@ OpenComputers II is a Minecraft mod adding virtual computers to the game. These This mod is a successor to [OpenComputers]. At least in spirit. While many of the implementation details have changed quite dramatically, the core concepts of customizable hardware, persistence and sand-boxing are shared. +## Gameplay Documentation +For documentation on how the to get computers up and running, and how to use them, see the [documentation]. It is available as a manual item in the game. + +## Development Documentation While the mod isn't quite yet ready for release due to some remaining technical and usability issues, the API should be mostly stable at this point. For most people the high level device API will be sufficient, and is much more accessible. It centers around the [`RPCDevice`][RPC Device]. For a sample block implementation, see the [redstone interface]. For a sample item implementation, see the [sound card]. If you wish to dive deeper, and provide emulated hardware that requires a Linux driver, this centers around the [`VMDevice`][VM Device]. For a sample block implementation, see the [disk drive]. For a sample item implementation, see the [network card]. -For documentation on how the to get computers up and running, and how to use them, see the [documentation]. It is available as a manual item in the game. +## Building +To build this project, credentials for the Github Package Repository must be set up once on your machine (see +[the documentation][GithubPackagesGradle] for more information). In short, you'll want to add your username and a +public access token with `read:packages` permissions into your `~/.gradle/gradle.properties`. The properties must be +named `gpr.user` and `gpr.key`. [OpenComputers]: https://github.com/MightyPirates/OpenComputers [RPC Device]: src/main/java/li/cil/oc2/api/bus/device/rpc/RPCDevice.java -[redstone interface]: src/main/java/li/cil/oc2/common/tileentity/RedstoneInterfaceTileEntity.java -[sound card]: src/main/java/li/cil/oc2/common/bus/device/item/SoundCardItemDevice.java +[redstone interface]: src/main/java/li/cil/oc2/common/blockentity/RedstoneInterfaceBlockEntity.java +[sound card]: src/main/java/li/cil/oc2/common/bus/device/rpc/item/SoundCardItemDevice.java [VM Device]: src/main/java/li/cil/oc2/api/bus/device/vm/VMDevice.java -[disk drive]: src/main/java/li/cil/oc2/common/tileentity/DiskDriveTileEntity.java -[network card]: src/main/java/li/cil/oc2/common/bus/device/item/NetworkInterfaceCardItemDevice.java -[documentation]: src/main/resources/assets/oc2/doc/en_us/index.md \ No newline at end of file +[disk drive]: src/main/java/li/cil/oc2/common/blockentity/DiskDriveBlockEntity.java +[network card]: src/main/java/li/cil/oc2/common/bus/device/vm/item/NetworkInterfaceCardDevice.java +[documentation]: src/main/resources/assets/oc2/doc/en_us/index.md +[GithubPackagesGradle]: https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-gradle-registry diff --git a/build.gradle b/build.gradle index 26db149b9..0062b9140 100644 --- a/build.gradle +++ b/build.gradle @@ -5,6 +5,7 @@ buildscript { } dependencies { classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '5.1.+', changing: true + classpath group: 'org.spongepowered', name: 'mixingradle', version: '0.7.+' } } @@ -15,6 +16,8 @@ plugins { } apply plugin: 'net.minecraftforge.gradle' +apply plugin: 'org.spongepowered.mixin' +apply from: 'minecraft.gradle' def getGitRef() { try { @@ -29,49 +32,53 @@ def getGitRef() { } } -final def semver = "${version_major}.${version_minor}.${version_patch}" - -String build_number = System.getenv('PROMOTED_NUMBER') -if (build_number == null) - build_number = System.getenv('BUILD_NUMBER') -if (build_number == null) - build_number = getGitRef() - -version = "${semver}+${build_number}" +version = "${semver}+${getGitRef()}" group = 'li.cil.oc2' -java.toolchain.languageVersion = JavaLanguageVersion.of(8) +java.toolchain { + languageVersion = JavaLanguageVersion.of(17) + vendor = JvmVendorSpec.ADOPTOPENJDK +} + +tasks.withType(JavaCompile).configureEach { + options.encoding = 'utf-8' +} repositories { mavenCentral() - maven { url 'https://dvs1.progwml6.com/files/maven' } // JEI - maven { url 'https://maven.cil.li/' } // Sedna + maven { + url 'https://cursemaven.com' + content { includeGroup 'curse.maven' } + } + maven { + url 'https://dvs1.progwml6.com/files/maven/' + content { includeGroup 'mezz.jei' } + } + ["fnuecke/SednaMinecraft"].forEach(repo -> { + maven { + url = uri("https://maven.pkg.github.com/${repo}") + credentials { + username = project.findProperty("gpr.user") ?: System.getenv("GPR_USER") + password = project.findProperty("gpr.key") ?: System.getenv("GPR_KEY") + } + } + }) } dependencies { minecraft "net.minecraftforge:forge:${minecraft_version}-${forge_version}" + annotationProcessor 'org.spongepowered:mixin:0.8.5:processor' - compileOnly 'org.jetbrains:annotations:21.0.1' - - implementation "li.cil.sedna:sedna-${minecraft_version}-forge:1.0.0+" - - // These three will be provided by sedna-mc in standalone. - implementation 'li.cil.ceres:ceres:0.0.3+' - implementation 'li.cil.sedna:sedna:1.0.1+' - implementation 'li.cil.sedna:sedna-buildroot:0.0.1+15' - - compileOnly fg.deobf("li.cil.markdown_manual:markdown_manual-${minecraft_version}-forge:${manual_version}:api") - runtimeOnly fg.deobf("li.cil.markdown_manual:markdown_manual-${minecraft_version}-forge:${manual_version}") + implementation "li.cil.sedna:sedna-${minecraft_version}-forge:1.0.13" - compileOnly fg.deobf("mezz.jei:jei-${jei_minecraft_version}:${jei_version}:api") - runtimeOnly fg.deobf("mezz.jei:jei-${jei_minecraft_version}:${jei_version}") + implementation fg.deobf("curse.maven:markdownmanual-502485:3738124") - testImplementation 'li.cil.ceres:ceres:0.0.3+' - testImplementation 'li.cil.sedna:sedna:1.0.1+' + compileOnly fg.deobf("mezz.jei:jei-${minecraft_version}:9.7.0.180:api") + runtimeOnly fg.deobf("mezz.jei:jei-${minecraft_version}:9.7.0.180") - testImplementation 'org.mockito:mockito-core:2.+' - testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.1' - testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.3.1' + testImplementation 'org.mockito:mockito-inline:4.3.1' + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2' } task packageScripts(type: Zip) { @@ -80,15 +87,22 @@ task packageScripts(type: Zip) { from 'src/main/scripts' } +task copyLicensesToResources(type: Copy) { + from '.' + into file("$buildDir/resources/main") + include "LICENSE*" +} + processResources.dependsOn(packageScripts) +processResources.dependsOn(copyLicensesToResources) minecraft { - mappings channel: mappings_channel, version: mappings_version + mappings channel: 'official', version: minecraft_version - runs { - client { - workingDirectory project.file('run') + accessTransformer = file('src/main/resources/META-INF/accesstransformer.cfg') + runs { + all { property 'forge.logging.markers', 'REGISTRIES' property 'forge.logging.console.level', 'info' @@ -99,36 +113,29 @@ minecraft { } } - server { + client { workingDirectory project.file('run') + } - property 'forge.logging.markers', 'REGISTRIES' - property 'forge.logging.console.level', 'info' - - mods { - oc2 { - source sourceSets.main - } - } + server { + workingDirectory project.file('run') + arg "--nogui" } data { workingDirectory project.file('run') - - property 'forge.logging.markers', 'REGISTRIES' - property 'forge.logging.console.level', 'info' - args '--mod', 'oc2', '--all', '--output', file('src/generated/resources/'), '--existing', file('src/main/resources') - - mods { - oc2 { - source sourceSets.main - } - } } } } +mixin { + add sourceSets.main, 'mixins.oc2.refmap.json' + config 'mixins.oc2.json' + +// quiet +} + task copyGeneratedResources(type: Copy) { from 'src/generated' into 'src/main' @@ -140,13 +147,15 @@ jar { manifest { attributes([ - 'Specification-Title' : 'oc2', - 'Specification-Vendor' : 'Sangar', - 'Specification-Version' : '1', - 'Implementation-Title' : project.name, - 'Implementation-Version' : "${semver}", - 'Implementation-Vendor' : 'Sangar', - 'Implementation-Timestamp': new Date().format("yyyy-MM-dd'T'HH:mm:ssZ") + 'FMLAT' : 'accesstransformer.cfg', + 'Specification-Title' : 'oc2', + 'Specification-Vendor' : 'Sangar', + 'Specification-Version' : '1', + 'Implementation-Title' : project.name, + 'Implementation-Version' : "${semver}", + 'Implementation-Vendor' : 'Sangar', + 'Implementation-Timestamp': new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"), + 'MixinConfigs' : 'mixins.oc2.json', ]) } } @@ -165,29 +174,38 @@ artifacts { publishing { publications { mavenJava(MavenPublication) { + groupId = project.group + artifactId = project.name + version = semver artifact jar artifact apiJar } } repositories { maven { - url System.getenv('MAVEN_PATH') + name = "GitHubPackages" + url = System.getenv("GITHUB_MAVEN_URL") ?: "" + credentials { + username = System.getenv("GITHUB_ACTOR") + password = System.getenv("GITHUB_TOKEN") + } } } } curseforge { - apiKey = project.hasProperty('curseForgeApiKey') ? project.curseForgeApiKey : '' + apiKey = System.getenv('CURSEFORGE_API_KEY') ?: "" project { id = curse_project_id - releaseType = curse_project_releaseType + releaseType = System.getenv('CURSEFORGE_RELEASE_TYPE') ?: "alpha" changelogType = 'markdown' - changelog = file('changelog.md') + changelog = System.getenv("CHANGELOG") ?: "Changelog not available." addGameVersion 'Forge' addGameVersion minecraft_version - addGameVersion 'Java 8' + addGameVersion 'Java 17' relations { requiredDependency 'markdownmanual' + requiredDependency 'sedna' } } } @@ -202,4 +220,8 @@ idea { test { useJUnitPlatform() -} \ No newline at end of file +} + +compileJava { + options.encoding = "UTF-8" +} diff --git a/gradle.properties b/gradle.properties index 55ff1e764..03f547f83 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,19 +3,8 @@ org.gradle.jvmargs=-Xmx3G org.gradle.daemon=false -minecraft_version=1.16.5 -mappings_channel=official -mappings_version=1.16.5 -forge_version=36.1.32 +forge_version=40.0.40 -version_major=0 -version_minor=0 -version_patch=1 +semver=0.0.0 -manual_version=1.1.0+ - -jei_minecraft_version=1.16.4 -jei_version=7.6.1.71 - -curse_project_id=0 -curse_project_releaseType=release +curse_project_id=437654 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e708b1c02..7454180f2 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 541658515..41dfb8790 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-bin.zip diff --git a/gradlew b/gradlew index 86c97c2b5..1b6c78733 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ -#!/usr/bin/env sh +#!/bin/sh # -# Copyright 2015 the original author or authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,67 +17,101 @@ # ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +APP_BASE_NAME=${0##*/} # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -87,9 +121,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -98,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" + JAVACMD=java which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the @@ -106,80 +140,95 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=`expr $i + 1` + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index 43f174ce7..107acd32c 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -33,7 +33,7 @@ set APP_HOME=%DIRNAME% for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome diff --git a/minecraft.gradle b/minecraft.gradle new file mode 100644 index 000000000..03af55905 --- /dev/null +++ b/minecraft.gradle @@ -0,0 +1,4 @@ +ext { + minecraft_version = '1.18.2' + minecraft_sdk = 'forge' +} diff --git a/settings.gradle b/settings.gradle index 400b201cc..735bdbc23 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,2 @@ -rootProject.name = 'oc2-1.16.5-forge' \ No newline at end of file +apply from: 'minecraft.gradle' +rootProject.name = "oc2-${minecraft_version}-${minecraft_sdk}" diff --git a/src/main/java/li/cil/oc2/api/API.java b/src/main/java/li/cil/oc2/api/API.java index 73f4c58b1..cff37dfca 100644 --- a/src/main/java/li/cil/oc2/api/API.java +++ b/src/main/java/li/cil/oc2/api/API.java @@ -1,3 +1,5 @@ +/* SPDX-License-Identifier: MIT */ + package li.cil.oc2.api; import com.google.gson.GsonBuilder; diff --git a/src/main/java/li/cil/oc2/api/README.md b/src/main/java/li/cil/oc2/api/README.md new file mode 100644 index 000000000..dbf62c69b --- /dev/null +++ b/src/main/java/li/cil/oc2/api/README.md @@ -0,0 +1,352 @@ +# The OpenComputer II API + +Welcome to the API of `oc2`, fellow developer! This document will hopefully provide a sufficient overview of what +integrations this API allows, and how to best implement them. The primary purpose of the API is to allow other mods to +implement their own devices, to be used by the computers in this mod. + +## The `RPCDevice` + +The core of the `RPCDevice` system is the [`RPCDevice`](bus/device/rpc/RPCDevice.java) interface itself. It defines a +list of [`RPCMethods`](bus/device/rpc/RPCMethod.java), which represent the methods that can be called on the device. +This is the suggested type of device to add, and allows easily exposing methods in your classes to virtual machines. + +> If you've seen the APIs of OpenComputers or ComputerCraft before, this should feel fairly familiar. + +### The `ObjectDevice` + +It is perfectly fine to implement these interfaces manually. There is a more convenient way, however, when adding a +device explicitly for this mod, in the form of the [`ObjectDevice`](bus/device/object/ObjectDevice.java). This class +allows wrapping a Java object as an `RPCDevice`. Methods to be exposed in the device are defined by adding the +[`Callback`](bus/device/object/Callback.java) annotation to methods of the object's class. + +### Type Names + +In addition to methods, `RPCDevices` provide a list of "type names". These names are meta-data, that can be used by +programs running in the virtual machines to identify devices. These should be clear and unique, to avoid confusion +between device types. For example, "machine" would probably be a little too generic, whereas "redstone_furnace" would +probably be a little better. Note that for all `BlockEntities` providing devices, their registry name is automatically +added to the list of type names. Equally, for all `Items` providing devices, their registry name is automatically added +to the list of type names. + +### Method Name Collisions + +All `RPCDevices` found for a particular `BlockEntity` or `Item` will be merged, and present as one singular `RPCDevice` +to the virtual machine. This means that not only type names are merged, but `RPCMethodGroups` are merged into a single +list as well. In most cases, it is fine to return each `RPCMethod` as its own `RPCMethodGroup`. For this reason, the +`RPCMethod` interface extends the `RPCMethodGroup` interface. + +The system supports method overloading to some degree. `RPCMethodGroups` with matching method name and parameter count +are queried one by one, for a method matching a list of parameters. `RPCMethods` provide a default implementation for +this, using the declared parameter types to determine if they match. + +However, RPCs are passed from VM to Java as JSON messages, so some overloads, that are clearly different on the Java +side, may lead to ambiguity. Specifically, in cases where one JSON serialization can be deserialized into different +types. Most problematic in this area are `null` values, since they match any object type parameter. + +> The system does a best-effort attempt: it will try deserializing parameters for ambiguous overloads one after +> the other, until deserialization for all parameter types succeeds. + +To avoid ambiguity, it is recommended to pick clear and unique method names where reasonable. This is particularly true +for generic `RPCDevices`, e.g. devices providing access to common capabilities, which may be provided by various +`BlockEntities`. An example for this are the built-in devices for the `IEnergyStorage` capability. + +For more control, `RPCMethodGroups` may implement custom override resolution via `findOverload(RPCInvocation)`. + +### Device Lifecycle + +Where needed, the optional interface methods `mount()`, `unmount()` and `suspend()` may be implemented, to react to +device lifecycle events. This can be useful in case some state needs to be initialized or reset, when the computer +starts or stops, or the device is connected to or disconnected from a computer. + +These methods are called in the following cases: + +- `mount()` is called, when a device is added to a running computer, or the computer it was added to, starts running. It + is also called when a computer resumes running after the chunk it sits in is loaded. +- `unmount()` is called, when a device removed from a running computer, or the computer it was added to, stop running. +- `suspend()` is called, when a device is connected to a running computer, and the chunk the computer is in is unloaded. + Either due to chunk unload or world unload. + +This can be useful for various things. For example: + +- Setting a flag in the block the device is associated with. + - Set the flag in `mount()`. + - Unset the flag `unmount()`. + - Ignore `suspend()`. +- Track out-of-minecraft resources, such as a file with extra data. + - Create and open the file in `mount()`. + - Close and delete the file in `unmount()`. + - Close the file in `suspend()`. + +### No Active Back-channel + +Unlike some other computer mods (e.g. OpenComputers and ComputerCraft), there is no *active* back-channel in the +`RPCDevice` API. In other words, it is not possible for `RPCDevices` to raise events in the virtual machines. The only +way to provide data to the virtual machines is as values returned from exposed methods. Programs running in the virtual +machines will always have to poll for changed data. + +## The `BlockDeviceProvider` and `ItemDeviceProvider` + +So let's say you have some `RPCDevice` at hand (or a `VMDevice`). Now you want the computer to use it. The core +functionality that makes `Devices` available to the mod are +the [`BlockDeviceProvider`](bus/device/provider/BlockDeviceProvider.java) and +the [`ItemDeviceProvider`](bus/device/provider/ItemDeviceProvider.java) interfaces. + +There exists a registry for each, with which all block and item providers must be registered. These registries are +queried to collect devices for a given block in the world, or an item in a machine inventory. + +### Block Devices + +Blocks devices are queried for all blocks adjacent to a `Bus Interface` that is connected to some computer via some +`Bus Cable` and another `Bus Interface`. Connected `Bus Cables` with attached `Bus Interfaces` define +a [`DeviceBus`](bus/DeviceBus.java). Computers collect all devices attached to the `DeviceBus` and make them available +to the virtual machine they run. Each registered `BlockDeviceProvider` is queried for a block in question, and the found +`RPCDevices` are aggregated into one `RPCDevice` proxy. + +> `BusInterfaces` look for `Devices` using `BlockDeviceProviders`. + +The mod comes with a set of convenience `BlockDeviceProviders`, which enable offering devices in various ways. This +means you don't necessarily have to implement your own provider. The following built-in providers exist: + +- `BlockEntities` are queried for the `Device` capability. If there is one, the returned device is used. + - This allows optional support for this mod, based on whether it is present or not. +- `Blocks` and `BlockEntities` are scanned for `Callbacks`. If there are any, they are wrapped in an `ObjectDevice`. + - This implies a hard dependency on this mod, due to the use of the `Callback` annotation in your `Block` + /`BlockEntity` code. + +### Item Devices + +Item devices are queried for items inserted into computers and robots. For each `ItemStack` in a device slot, each +`ItemDeviceProvider` is queried for the item in question, and the found `RPCDevices` are aggregated into one `RPCDevice` +proxy. + +> Note that such items must be tagged with the slot type they fit into, or they cannot be placed into computers and robots. + +## The `VMDevice` + +`VMDevices` are low-level, memory-mapped devices, emulating "real" hardware, and thus requiring driver support by the +operating system running in the virtual machines. + +> `VMDevices` are very low-level, and something most people can ignore. + +The core of the `VMDevice` system is the [`VMDevice`](bus/device/vm/VMDevice.java) interface itself. It defines a proxy +used to load and unload actual emulated hardware. `VMDevices` use +the [`VMContext`](bus/device/vm/context/VMContext.java) to properly bind hardware to the virtual machine upon +initialization. This typically includes reserving an address block in memory, possibly hooking up interrupts and +reserving host memory from the memory tracker. In most cases, `VMDevices` will add a `MemoryMappedDevice` to +the `MemoryMap`, an interface used by [Sedna], the VM implementation used to run the computers in this mod. + +On the off chance you wish to add a `VMDevice`, and the existing devices do not suffice for reference, open a discussion +on Github. I'll skip more details here, since I doubt most people would care, and it might instead scare people off... + +## Examples + +These examples are roughly sorted in order of likely usefulness. Most mods will want to maintain a optional integration +with this mod, instead of a hard dependency, so these examples are shown first. + +### Block Device for own `BlockEntity` + +In this example, a device is made available for a custom `BlockEntity`. + +Using capabilities: + +```java +import li.cil.oc2.api.bus.device.object.Callback; +import li.cil.oc2.api.bus.device.object.ObjectDevice; +import li.cil.oc2.api.bus.device.rpc.RPCDevice; +import net.minecraft.core.Direction; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.common.util.LazyOptional; +import net.minecraftforge.fml.ModList; + +class ModBlockEntity extends BlockEntity { + public int getMagicValue() { + // ... + } + + @Override + public LazyOptional getCapability(Capability cap, @Nullable Direction side) { + if (ModList.get().isLoaded("oc2")) { + // Note: you can also store this and invalidate the capability to remove the + // device/trigger the device bus to scan for changes in available devices. + LazyOptional device = getDeviceCapability(); + if (device.isPresent()) { + return device; + } + } + return super.getCapability(cap, side); + } + + private LazyOptional getDeviceCapability(Capability cap) { + if (cap == Integration.DEVICE_CAPABILITY) { + LazyOptional.of(() -> Integration.createDevice(this)).cast(); + } else { + return LazyOptional.empty(); + } + } +} + +class Integration { + public static final Capability DEVICE_CAPABILITY = CapabilityManager.get(new CapabilityToken<>() { }); + + public static RPCDevice createDevice(ModBlockEntity blockEntity) { + return new ObjectDevice(new ModBlockEntityDevice(blockEntity)); + } + + // Note: this being a record is relevant, as it implements equals() for us. When manually implementing devices, + // overriding equals() is strongly recommended, to allow newly picked up devices to be matched to previously + // existing devices. Otherwise, the devices will technically be removed and re-added every time the device bus + // scans for device changes. This is particularly relevant when using the lifecycle methods mount(), unmount() + // and suspend() (e.g. if we were to implement LifecycleAwareDevice on this record). + record ModBlockEntityDevice(ModBlockEntity blockEntity) { + @Callback + public int getMagicValue() { + return blockEntity.getMagicValue(); + } + } +} +``` + +Using the `Callback` annotation in the `BlockEntity` (hard dependency): + +```java +import li.cil.oc2.api.bus.device.object.Callback; +import net.minecraft.world.level.block.entity.BlockEntity; + +class ModBlockEntity extends BlockEntity { + @Callback + public int getMagicValue() { + // ... + } +} +``` + +Using a custom `BlockDeviceProvider` is also possible, this is equivalent to the following example, on how to add +devices to third-party `BlockEntities`. + +### Block Device for a Third-Party `BlockEntity` + +In this example, a simple device providing a single method, `squareRoot`, is made available for the `FurnaceBlockEntity` +. As long as the registration of the `BlockDeviceProvider` is gated behind a check, whether `oc2` is present, this is a +soft dependency. + +Using `ObjectDevice`: + +```java +import li.cil.oc2.api.bus.device.object.Callback; +import li.cil.oc2.api.bus.device.object.ObjectDevice; +import li.cil.oc2.api.bus.device.rpc.RPCDevice; +import net.minecraft.world.level.block.entity.BlockEntity; + +class MyCalculatorDevice { + @Callback(synchronize = false) + public int squareRoot(int value) { + if (value < 0) throw new IllegalArgumentException("Invalid input value!"); + return Math.sqrt(value); + } +} + +class ModDeviceProvider extends ForgeRegistryEntry implements BlockDeviceProvider { + @Override + public Invalidatable getDevice(BlockDeviceQuery query) { + // Note: optionally check other conditions, such as settings, on whether to just return empty(). + BlockEntity blockEntity = query.getLevel().getBlockEntity(query.getQueryPosition()); + if (blockEntity instanceof FurnaceBlockEntity) { + return Invalidatable.of(new ObjectDevice(new MyCalculatorDevice(), "my_calculator_device")); + } else { + return Invalidatable.empty(); + } + } +} +``` + +Using the `RPCDevice` and `RPCMethods` interfaces directly: + +```java +import li.cil.oc2.api.bus.device.Device; +import li.cil.oc2.api.bus.device.provider.BlockDeviceProvider; +import li.cil.oc2.api.bus.device.provider.BlockDeviceQuery; +import li.cil.oc2.api.bus.device.rpc.RPCDevice; +import li.cil.oc2.api.bus.device.rpc.RPCMethod; +import li.cil.oc2.api.bus.device.rpc.RPCMethodGroup; +import li.cil.oc2.api.bus.device.rpc.RPCParameter; +import li.cil.oc2.api.util.Invalidatable; +import net.minecraft.world.level.block.entity.FurnaceBlockEntity; + +import java.util.Collections; +import java.util.List; + +class ModDevice implements RPCDevice { + @Override + public List getTypeNames() { + return Collections.singletonList("my_calculator_device"); + } + + @Override + public List getMethodGroups() { + return Collections.singletonList(new RPCMethod() { + @Override + public String getName() { + return "squareRoot"; + } + + @Override + public boolean isSynchronized() { + return false; + } + + @Override + public Class getReturnType() { + return int.class; + } + + @Override + public RPCParameter[] getParameters() { + return new RPCParameter[]{() -> int.class}; + } + + @Override + public Object invoke(RPCInvocation invocation) { + int arg = invocation.getParameters().get(0).getAsInt(); + if (arg < 0) throw new IllegalArgumentException("Invalid input value!"); + return Math.sqrt(arg); + } + }); + } +} + +class ModDeviceProvider extends ForgeRegistryEntry implements BlockDeviceProvider { + @Override + public Invalidatable getDevice(BlockDeviceQuery query) { + // Note: optionally check other conditions, such as settings, on whether to just return empty(). + BlockEntity blockEntity = query.getLevel().getBlockEntity(query.getQueryPosition()); + if (blockEntity instanceof FurnaceBlockEntity) { + return Invalidatable.of(new ModDevice()); + } else { + return Invalidatable.empty(); + } + } +} +``` + +Shared device provider registration: + +```java +import li.cil.oc2.api.bus.device.provider.BlockDeviceProvider; +import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; +import net.minecraftforge.registries.DeferredRegister; + +class Providers { + static final DeferredRegister BLOCK_DEVICE_PROVIDERS = + DeferredRegister.create(BlockDeviceProvider.class, "my_mod_id"); + + // Called from mod initialization, if oc2 is present. + static void initialize() { + BLOCK_DEVICE_PROVIDERS.register("my_calculator_device", ModDeviceProvider::new); + + BLOCK_DEVICE_PROVIDERS.register(FMLJavaModLoadingContext.get().getModEventBus()); + } +} +``` + +[Sedna]: https://github.com/fnuecke/sedna diff --git a/src/main/java/li/cil/oc2/api/bus/BlockDeviceBusElement.java b/src/main/java/li/cil/oc2/api/bus/BlockDeviceBusElement.java index 3fc7e586a..29129e3f9 100644 --- a/src/main/java/li/cil/oc2/api/bus/BlockDeviceBusElement.java +++ b/src/main/java/li/cil/oc2/api/bus/BlockDeviceBusElement.java @@ -1,7 +1,11 @@ +/* SPDX-License-Identifier: MIT */ + package li.cil.oc2.api.bus; -import net.minecraft.util.math.BlockPos; -import net.minecraft.world.IWorld; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.LevelAccessor; + +import javax.annotation.Nullable; /** * Implementing this interface allows providing positional information to the {@link DeviceBusController}. @@ -12,11 +16,12 @@ */ public interface BlockDeviceBusElement extends DeviceBusElement { /** - * The world the bus lives in. + * The level the bus lives in. * - * @return the world the bus lives in. + * @return the level the bus lives in. */ - IWorld getLevel(); + @Nullable + LevelAccessor getLevel(); /** * The position of this bus element. diff --git a/src/main/java/li/cil/oc2/api/bus/DeviceBus.java b/src/main/java/li/cil/oc2/api/bus/DeviceBus.java index 6bb7f3eea..cbbc90b6a 100644 --- a/src/main/java/li/cil/oc2/api/bus/DeviceBus.java +++ b/src/main/java/li/cil/oc2/api/bus/DeviceBus.java @@ -1,3 +1,5 @@ +/* SPDX-License-Identifier: MIT */ + package li.cil.oc2.api.bus; import li.cil.oc2.api.bus.device.Device; diff --git a/src/main/java/li/cil/oc2/api/bus/DeviceBusController.java b/src/main/java/li/cil/oc2/api/bus/DeviceBusController.java index 023ba6c4d..d8ab81268 100644 --- a/src/main/java/li/cil/oc2/api/bus/DeviceBusController.java +++ b/src/main/java/li/cil/oc2/api/bus/DeviceBusController.java @@ -1,3 +1,5 @@ +/* SPDX-License-Identifier: MIT */ + package li.cil.oc2.api.bus; import li.cil.oc2.api.bus.device.Device; @@ -29,17 +31,33 @@ * @see DeviceBusElement */ public interface DeviceBusController { + /** + * Reason for a bus scan. Used to determine whether to immediately perform the scan or no, for example. + */ + enum ScanReason { + BUS_CHANGE, + BUS_ERROR, + } + /** * Schedules a scan. *

- * This will immediately invalidate the current bus, i.e. all {@link DeviceBusElement}s - * will be removed from the controller and {@link #getDevices()} will return an empty - * list after this call. + * Multiple sequential calls to this method do nothing, the actual scan will be performed + * in the next update. + * + * @param reason the reason for the bus scan. + */ + void scheduleBusScan(ScanReason reason); + + /** + * Schedules a scan due to a bus configuration change. *

* Multiple sequential calls to this method do nothing, the actual scan will be performed * in the next update. */ - void scheduleBusScan(); + default void scheduleBusScan() { + scheduleBusScan(ScanReason.BUS_CHANGE); + } /** * Forces a device map rebuild. diff --git a/src/main/java/li/cil/oc2/api/bus/DeviceBusElement.java b/src/main/java/li/cil/oc2/api/bus/DeviceBusElement.java index daa89be47..53d11696c 100644 --- a/src/main/java/li/cil/oc2/api/bus/DeviceBusElement.java +++ b/src/main/java/li/cil/oc2/api/bus/DeviceBusElement.java @@ -1,3 +1,5 @@ +/* SPDX-License-Identifier: MIT */ + package li.cil.oc2.api.bus; import li.cil.oc2.api.bus.device.Device; @@ -18,7 +20,7 @@ * connected to this element. *

* This interface is relevant when implementing means to extend the bus, e.g. - * to provide a custom cable implementation or some kind of a device container. + * to provide a custom cable implementation or some kind of device container. *

* Implementations must call {@link #scheduleScan()} when they become * invalid, e.g. due to being in a chunk that is being unloaded or the block @@ -31,7 +33,7 @@ public interface DeviceBusElement extends DeviceBus { *

* This will be called by {@link DeviceBusController}s when scanning. *

- * Bus elements can be have multiple controllers at the same time. This is used + * Bus elements can have multiple controllers at the same time. This is used * by controllers to detect each other on the bus. *

* When {@link #scheduleScan()} is called, {@link DeviceBusController#scheduleBusScan()} @@ -89,7 +91,7 @@ public interface DeviceBusElement extends DeviceBus { * track of the device. Note that some device types (e.g. {@link RPCDevice}s) * require for an ID to be provided for them to work at all. *

- * It is possible for multiple devices to have the same identifier. Typically + * It is possible for multiple devices to have the same identifier. Typically, * this means they represent a view on the same underlying object. How this is * handled depends on the device type and may or may not be supported. *

diff --git a/src/main/java/li/cil/oc2/api/bus/device/Device.java b/src/main/java/li/cil/oc2/api/bus/device/Device.java index a6da78a04..6a6b699b8 100644 --- a/src/main/java/li/cil/oc2/api/bus/device/Device.java +++ b/src/main/java/li/cil/oc2/api/bus/device/Device.java @@ -1,8 +1,12 @@ +/* SPDX-License-Identifier: MIT */ + package li.cil.oc2.api.bus.device; import li.cil.oc2.api.bus.DeviceBus; import li.cil.oc2.api.bus.DeviceBusController; -import net.minecraft.nbt.CompoundNBT; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraftforge.common.util.INBTSerializable; /** @@ -11,17 +15,39 @@ * Which types are handled/supported by a bus depends on context of the {@link DeviceBusController} * managing the bus. *

+ * May be provided as a capability on {@link BlockEntity}s and {@link ItemStack}s. + *

* Note that it is strongly encouraged for implementations to provide an overloaded - * {@link Object#equals(Object)} and {@link Object#hashCode()} so that identical devices can be - * detected. + * {@link Object#equals(Object)} and {@link Object#hashCode()} so that identical + * devices can be detected. */ -public interface Device extends INBTSerializable { +public interface Device extends INBTSerializable { + /** + * Called to dispose this device. + *

+ * Called when the connected virtual machine stops or the device is removed from a {@link DeviceBus}. + */ + default void dispose() { + } + + /** + * Called to serialize this device into its container's persistent storage. + * + * @return the serialized state of this device. + */ @Override - default CompoundNBT serializeNBT() { - return new CompoundNBT(); + default CompoundTag serializeNBT() { + return new CompoundTag(); } + /** + * Called to deserialize this device from its container's persistent storage. + *

+ * The passed tag will be what was last returned by {@link #serializeNBT()}. + * + * @param tag the serialized state of this device. + */ @Override - default void deserializeNBT(final CompoundNBT tag) { + default void deserializeNBT(final CompoundTag tag) { } } diff --git a/src/main/java/li/cil/oc2/api/bus/device/DeviceType.java b/src/main/java/li/cil/oc2/api/bus/device/DeviceType.java index 9e90d0a90..7730b3e43 100644 --- a/src/main/java/li/cil/oc2/api/bus/device/DeviceType.java +++ b/src/main/java/li/cil/oc2/api/bus/device/DeviceType.java @@ -1,8 +1,14 @@ +/* SPDX-License-Identifier: MIT */ + package li.cil.oc2.api.bus.device; import li.cil.oc2.api.API; -import net.minecraft.util.ResourceLocation; -import net.minecraft.util.text.ITextComponent; +import net.minecraft.core.Registry; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; import net.minecraftforge.registries.IForgeRegistryEntry; /** @@ -16,7 +22,14 @@ public interface DeviceType extends IForgeRegistryEntry { /** * The registry name of the registry holding device types. */ - ResourceLocation REGISTRY = new ResourceLocation(API.MOD_ID, "device_type"); + ResourceKey> REGISTRY = ResourceKey.createRegistryKey(new ResourceLocation(API.MOD_ID, "device_type")); + + /** + * The tag representing this device type. + * + * @return the item tag. + */ + TagKey getTag(); /** * An icon rendered as background of empty slots, visually indicating the @@ -32,5 +45,5 @@ public interface DeviceType extends IForgeRegistryEntry { * * @return the display name for this device type. */ - ITextComponent getName(); + Component getName(); } diff --git a/src/main/java/li/cil/oc2/api/bus/device/DeviceTypes.java b/src/main/java/li/cil/oc2/api/bus/device/DeviceTypes.java index a87df0221..68fb72f6c 100644 --- a/src/main/java/li/cil/oc2/api/bus/device/DeviceTypes.java +++ b/src/main/java/li/cil/oc2/api/bus/device/DeviceTypes.java @@ -1,3 +1,5 @@ +/* SPDX-License-Identifier: MIT */ + package li.cil.oc2.api.bus.device; import li.cil.oc2.api.API; @@ -14,4 +16,5 @@ public final class DeviceTypes { @ObjectHolder("card") public static DeviceType CARD = null; @ObjectHolder("robot_module") public static DeviceType ROBOT_MODULE = null; @ObjectHolder("floppy") public static DeviceType FLOPPY = null; + @ObjectHolder("network_tunnel") public static DeviceType NETWORK_TUNNEL = null; } diff --git a/src/main/java/li/cil/oc2/api/bus/device/ItemDevice.java b/src/main/java/li/cil/oc2/api/bus/device/ItemDevice.java index 525df3aea..a9c2d9993 100644 --- a/src/main/java/li/cil/oc2/api/bus/device/ItemDevice.java +++ b/src/main/java/li/cil/oc2/api/bus/device/ItemDevice.java @@ -1,35 +1,39 @@ +/* SPDX-License-Identifier: MIT */ + package li.cil.oc2.api.bus.device; -import net.minecraft.nbt.CompoundNBT; +import li.cil.oc2.api.bus.device.provider.ItemDeviceProvider; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.item.ItemStack; /** - * Specialized device type provided by {@link li.cil.oc2.api.bus.device.provider.ItemDeviceProvider}s. + * Specialized device type provided by {@link ItemDeviceProvider}s. *

* This interface provides methods that allow the context in which an item based device is - * created to copy data from and to the {@link net.minecraft.item.ItemStack} the device was + * created to copy data from and to the {@link ItemStack} the device was * created for. *

- * By default, no data is copied to and from the {@link net.minecraft.item.ItemStack}. Use + * By default, no data is copied to and from the {@link ItemStack}. Use * these methods for storing data that should survive the item the device is based on being * removed from the context (e.g. a computer) and being put back in some context. */ public interface ItemDevice extends Device { /** - * Export data that should be copied to the {@link net.minecraft.item.ItemStack} + * Export data that should be copied to the {@link ItemStack} * this device was created for to a tag that will be stored in the item. * * @param nbt the tag that will be written to the item. */ - default void exportToItemStack(final CompoundNBT nbt) { + default void exportToItemStack(final CompoundTag nbt) { } /** - * Import data that is present on the {@link net.minecraft.item.ItemStack} this + * Import data that is present on the {@link ItemStack} this * device was created for. The provided tag corresponds to the one presented to - * the {@link #exportToItemStack(CompoundNBT)} method. + * the {@link #exportToItemStack(CompoundTag)} method. * * @param nbt the tag that was read from the item. */ - default void importFromItemStack(final CompoundNBT nbt) { + default void importFromItemStack(final CompoundTag nbt) { } } diff --git a/src/main/java/li/cil/oc2/api/bus/device/data/BlockDeviceData.java b/src/main/java/li/cil/oc2/api/bus/device/data/BlockDeviceData.java index ecf08c7c1..818b1ecd9 100644 --- a/src/main/java/li/cil/oc2/api/bus/device/data/BlockDeviceData.java +++ b/src/main/java/li/cil/oc2/api/bus/device/data/BlockDeviceData.java @@ -1,9 +1,9 @@ +/* SPDX-License-Identifier: MIT */ + package li.cil.oc2.api.bus.device.data; -import li.cil.oc2.api.API; import li.cil.sedna.api.device.BlockDevice; -import net.minecraft.util.ResourceLocation; -import net.minecraft.util.text.ITextComponent; +import net.minecraft.network.chat.Component; import net.minecraftforge.registries.IForgeRegistryEntry; /** @@ -26,11 +26,6 @@ * */ public interface BlockDeviceData extends IForgeRegistryEntry { - /** - * The registry name of the registry holding block device bases. - */ - ResourceLocation REGISTRY = new ResourceLocation(API.MOD_ID, "block_device_data"); - /** * Gets the read-only base block device this implementation describes. * @@ -44,5 +39,5 @@ public interface BlockDeviceData extends IForgeRegistryEntry { * * @return the display name of this block device. */ - ITextComponent getDisplayName(); + Component getDisplayName(); } diff --git a/src/main/java/li/cil/oc2/api/bus/device/data/Firmware.java b/src/main/java/li/cil/oc2/api/bus/device/data/Firmware.java index 5a857a4f8..503a83a73 100644 --- a/src/main/java/li/cil/oc2/api/bus/device/data/Firmware.java +++ b/src/main/java/li/cil/oc2/api/bus/device/data/Firmware.java @@ -1,9 +1,9 @@ +/* SPDX-License-Identifier: MIT */ + package li.cil.oc2.api.bus.device.data; -import li.cil.oc2.api.API; import li.cil.sedna.api.memory.MemoryMap; -import net.minecraft.util.ResourceLocation; -import net.minecraft.util.text.ITextComponent; +import net.minecraft.network.chat.Component; import net.minecraftforge.registries.IForgeRegistryEntry; /** @@ -21,17 +21,12 @@ * */ public interface Firmware extends IForgeRegistryEntry { - /** - * The registry name of the registry holding firmwares. - */ - ResourceLocation REGISTRY = new ResourceLocation(API.MOD_ID, "firmware"); - /** * Runs this firmware. *

* This will usually load machine code into memory at the specified start address. *

- * Typically only returns {@code false} when there was not enough memory to fit the firmware. + * Typically, only returns {@code false} when there was not enough memory to fit the firmware. * * @param memory access to the memory map of the machine. * @param startAddress the memory address where execution will commence. @@ -45,5 +40,5 @@ public interface Firmware extends IForgeRegistryEntry { * * @return the display name of this firmware. */ - ITextComponent getDisplayName(); + Component getDisplayName(); } diff --git a/src/main/java/li/cil/oc2/api/bus/device/data/package-info.java b/src/main/java/li/cil/oc2/api/bus/device/data/package-info.java index 1fc4966b0..47ed0659c 100644 --- a/src/main/java/li/cil/oc2/api/bus/device/data/package-info.java +++ b/src/main/java/li/cil/oc2/api/bus/device/data/package-info.java @@ -1,47 +1,9 @@ -/** - * The device bus is the glue that connects devices and VMs. - *

- * A bus must always be managed by a {@link li.cil.oc2.api.bus.DeviceBusController}. - * If there is no controller, there is no (connected) bus. - *

- * When a controller performs a scan, it collects a list of connected - * {@link li.cil.oc2.api.bus.DeviceBusElement}s thus defining a - * {@link li.cil.oc2.api.bus.DeviceBus}. - * How the controller scans for elements depends on the implementation. - * One example is a block-based controller which scans adjacent blocks - * in a recursive manner -- usually up to some maximum bus complexity. - *

- * {@link li.cil.oc2.api.bus.DeviceBusElement}s are responsible for - * providing a list of devices connected to them. Whether they play an - * active role and seek out devices, or passively expect devices to be - * registered with them depends on the implementation implementation. - *

- * After a scan {@link li.cil.oc2.api.bus.DeviceBusController}s then - * collect all devices from all bus elements to build a global set - * of devices on the bus. - *

- * There can be various types of devices on a bus, but which types are - * supported will depend on the context of the controller. Currently two - * types of devices are are defined in this API, {@link li.cil.oc2.api.bus.device.rpc.RPCDevice} - * and {@link li.cil.oc2.api.bus.device.vm.VMDevice}. - *

    - *
  • - * RPC devices are a high-level system for providing VMs with means of - * calling methods on such devices. The protocol used allows VMs to - * dynamically detect what devices are present and what methods may be - * called on them. - *
  • - *
  • - * VM devices are low-level devices that may register emulations of actual - * devices with a VM. Such devices will require drivers to be present - * inside the VM to work. - *
  • - *
- */ +/* SPDX-License-Identifier: MIT */ + @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault package li.cil.oc2.api.bus.device.data; -import mcp.MethodsReturnNonnullByDefault; +import net.minecraft.MethodsReturnNonnullByDefault; -import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/src/main/java/li/cil/oc2/api/bus/device/object/Callback.java b/src/main/java/li/cil/oc2/api/bus/device/object/Callback.java index f4cfa1ff1..ae3504141 100644 --- a/src/main/java/li/cil/oc2/api/bus/device/object/Callback.java +++ b/src/main/java/li/cil/oc2/api/bus/device/object/Callback.java @@ -1,3 +1,5 @@ +/* SPDX-License-Identifier: MIT */ + package li.cil.oc2.api.bus.device.object; import li.cil.oc2.api.bus.device.rpc.RPCMethod; @@ -27,8 +29,8 @@ * the caller as each call will take at least one tick (50ms). *

* Use this when the targeted method interacts with data that is not thread - * safe, for example the world or any objects inside the world, such as - * tile entities and entities. + * safe, for example the level or any objects inside the level, such as + * entities and block entities. * * @return {@code true} when to be executed on main thread; {@code false} otherwise. */ diff --git a/src/main/java/li/cil/oc2/api/bus/device/object/Callbacks.java b/src/main/java/li/cil/oc2/api/bus/device/object/Callbacks.java index 498cbe25b..2fccd3efd 100644 --- a/src/main/java/li/cil/oc2/api/bus/device/object/Callbacks.java +++ b/src/main/java/li/cil/oc2/api/bus/device/object/Callbacks.java @@ -1,7 +1,10 @@ +/* SPDX-License-Identifier: MIT */ + package li.cil.oc2.api.bus.device.object; import li.cil.oc2.api.bus.device.rpc.AbstractRPCMethod; import li.cil.oc2.api.bus.device.rpc.RPCMethod; +import li.cil.oc2.api.bus.device.rpc.RPCMethodGroup; import li.cil.oc2.api.bus.device.rpc.RPCParameter; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -27,9 +30,9 @@ public final class Callbacks { /////////////////////////////////////////////////////////////////// - private static final HashMap, List> METHOD_BY_TYPE = new HashMap<>(); - private static final HashMap PARAMETERS_BY_METHOD = new HashMap<>(); - private static final HashMap DOCUMENTATION_BY_METHOD = new HashMap<>(); + private static final Map, List> METHOD_BY_TYPE = Collections.synchronizedMap(new HashMap<>()); + private static final Map PARAMETERS_BY_METHOD = Collections.synchronizedMap(new HashMap<>()); + private static final Map DOCUMENTATION_BY_METHOD = Collections.synchronizedMap(new HashMap<>()); /////////////////////////////////////////////////////////////////// @@ -54,10 +57,10 @@ public final class Callbacks { * @param methodContainer an instance of a class with annotated methods. * @return the list of methods extracted from the specified object. */ - public static List collectMethods(final Object methodContainer) { + public static List collectMethods(final Object methodContainer) { final List reflectedMethods = getMethods(methodContainer.getClass()); - final ArrayList methods = new ArrayList<>(); + final ArrayList methods = new ArrayList<>(); for (final Method method : reflectedMethods) { try { methods.add(new ObjectRPCMethod(methodContainer, method)); @@ -79,8 +82,8 @@ public static List collectMethods(final Object methodContainer) { * @return {@code true} if any methods were found on the object; {@code false} otherwise. */ public static boolean hasMethods(final Object object) { - if (object instanceof Class) { - return !getMethods((Class) object).isEmpty(); + if (object instanceof final Class clazz) { + return !getMethods(clazz).isEmpty(); } else { return !getMethods(object.getClass()).isEmpty(); } @@ -91,8 +94,8 @@ public static boolean hasMethods(final Object object) { private static List getMethods(final Class type) { synchronized (METHOD_BY_TYPE) { return METHOD_BY_TYPE.computeIfAbsent(type, c -> Arrays.stream(c.getMethods()) - .filter(m -> m.isAnnotationPresent(Callback.class)) - .collect(Collectors.toList())); + .filter(m -> m.isAnnotationPresent(Callback.class)) + .collect(Collectors.toList())); } } @@ -115,7 +118,7 @@ private ObjectRPCMethod(final ConstructorData data) throws IllegalAccessExceptio @Nullable @Override - public Object invoke(final Object... parameters) throws Throwable { + protected Object invoke(final Object... parameters) throws Throwable { return handle.invokeWithArguments(parameters); } @@ -171,8 +174,7 @@ public ConstructorData(final Object target, final Method method) { String returnValueDescription = hasReturnValueDescription ? annotation.returnValueDescription() : null; final HashMap parameterDescriptions = new HashMap<>(); - if (target instanceof DocumentedDevice) { - final DocumentedDevice documentedDevice = (DocumentedDevice) target; + if (target instanceof final DocumentedDevice documentedDevice) { final DeviceVisitorImpl visitor = new DeviceVisitorImpl(); documentedDevice.getDeviceDocumentation(visitor); @@ -196,9 +198,9 @@ public ConstructorData(final Object target, final Method method) { this.returnValueDescription = documentation.returnValueDescription; this.parameters = PARAMETERS_BY_METHOD.computeIfAbsent(method, - m -> Arrays.stream(m.getParameters()) - .map(parameter -> new ReflectionParameter(parameter, documentation.parameterDescriptions)) - .toArray(RPCParameter[]::new)); + m -> Arrays.stream(m.getParameters()) + .map(parameter -> new ReflectionParameter(parameter, documentation.parameterDescriptions)) + .toArray(RPCParameter[]::new)); } } @@ -214,7 +216,7 @@ public ReflectionParameter(final java.lang.reflect.Parameter parameter, final Ha final boolean hasName = annotation != null && Strings.isNotBlank(annotation.value()); final boolean hasDescription = annotation != null && Strings.isNotBlank(annotation.description()); - this.name = hasName ? annotation.value() : null; + this.name = hasName ? annotation.value() : (parameter.isNamePresent() ? parameter.getName() : null); if (parameterDescriptions.containsKey(this.name)) { this.description = parameterDescriptions.get(this.name); @@ -242,17 +244,9 @@ public Optional getDescription() { } } - private static final class CallbackDocumentation { - @Nullable public final String description; - @Nullable public final String returnValueDescription; - public final HashMap parameterDescriptions; - - private CallbackDocumentation(@Nullable final String description, @Nullable final String returnValueDescription, final HashMap parameterDescriptions) { - this.description = description; - this.returnValueDescription = returnValueDescription; - this.parameterDescriptions = parameterDescriptions; - } - } + private record CallbackDocumentation(@Nullable String description, + @Nullable String returnValueDescription, + HashMap parameterDescriptions) { } private static final class DeviceVisitorImpl implements DocumentedDevice.DeviceVisitor { public final HashMap callbacks = new HashMap<>(); diff --git a/src/main/java/li/cil/oc2/api/bus/device/object/DocumentedDevice.java b/src/main/java/li/cil/oc2/api/bus/device/object/DocumentedDevice.java index 64010877b..f840bae09 100644 --- a/src/main/java/li/cil/oc2/api/bus/device/object/DocumentedDevice.java +++ b/src/main/java/li/cil/oc2/api/bus/device/object/DocumentedDevice.java @@ -1,3 +1,5 @@ +/* SPDX-License-Identifier: MIT */ + package li.cil.oc2.api.bus.device.object; /** diff --git a/src/main/java/li/cil/oc2/api/bus/device/object/LifecycleAwareDevice.java b/src/main/java/li/cil/oc2/api/bus/device/object/LifecycleAwareDevice.java new file mode 100644 index 000000000..aedd908bb --- /dev/null +++ b/src/main/java/li/cil/oc2/api/bus/device/object/LifecycleAwareDevice.java @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: MIT */ + +package li.cil.oc2.api.bus.device.object; + +import li.cil.oc2.api.bus.device.rpc.RPCDevice; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.block.entity.BlockEntity; + +/*** + * This interface is used to receive life-cycle callbacks on targets of an {@link ObjectDevice}. + *

+ * In particular, this also includes {@link BlockEntity}s and {@link Entity}s providing {@link Callback}s. + */ +public interface LifecycleAwareDevice { + /** + * This method corresponds to {@link RPCDevice#mount()}. + */ + default void onDeviceMounted() { + } + + /** + * This method corresponds to {@link RPCDevice#unmount()}. + */ + default void onDeviceUnmounted() { + } + + /** + * This method corresponds to {@link RPCDevice#dispose()}. + */ + default void onDeviceDisposed() { + } +} diff --git a/src/main/java/li/cil/oc2/api/bus/device/object/NamedDevice.java b/src/main/java/li/cil/oc2/api/bus/device/object/NamedDevice.java index 2227db851..0001a66fe 100644 --- a/src/main/java/li/cil/oc2/api/bus/device/object/NamedDevice.java +++ b/src/main/java/li/cil/oc2/api/bus/device/object/NamedDevice.java @@ -1,14 +1,16 @@ +/* SPDX-License-Identifier: MIT */ + package li.cil.oc2.api.bus.device.object; -import net.minecraft.block.Block; -import net.minecraft.tileentity.TileEntity; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntity; import java.util.Collection; /** * This interface is used to declare additional type names for a device on targets of an {@link ObjectDevice}. *

- * For example: {@link Block}s and {@link TileEntity}s that contain {@link Callback} methods may implement + * For example: {@link Block}s and {@link BlockEntity}s that contain {@link Callback} methods may implement * this interface to provide additional type names. */ public interface NamedDevice { diff --git a/src/main/java/li/cil/oc2/api/bus/device/object/ObjectDevice.java b/src/main/java/li/cil/oc2/api/bus/device/object/ObjectDevice.java index 4f25a333e..2e237764d 100644 --- a/src/main/java/li/cil/oc2/api/bus/device/object/ObjectDevice.java +++ b/src/main/java/li/cil/oc2/api/bus/device/object/ObjectDevice.java @@ -1,11 +1,16 @@ +/* SPDX-License-Identifier: MIT */ + package li.cil.oc2.api.bus.device.object; +import li.cil.oc2.api.bus.device.ItemDevice; import li.cil.oc2.api.bus.device.rpc.RPCDevice; import li.cil.oc2.api.bus.device.rpc.RPCMethod; +import li.cil.oc2.api.bus.device.rpc.RPCMethodGroup; import javax.annotation.Nullable; import java.util.ArrayList; import java.util.List; +import java.util.Locale; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; @@ -16,10 +21,10 @@ * annotation to discover {@link RPCMethod}s in a target object via * {@link Callbacks#collectMethods(Object)}. */ -public final class ObjectDevice implements RPCDevice { +public final class ObjectDevice implements RPCDevice, ItemDevice { private final Object object; private final ArrayList typeNames; - private final List methods; + private final List methods; private final String className; /////////////////////////////////////////////////////////////////// @@ -37,10 +42,12 @@ public ObjectDevice(final Object object, final List typeNames) { this.methods = Callbacks.collectMethods(object); this.className = object.getClass().getSimpleName(); - if (object instanceof NamedDevice) { - final NamedDevice namedDevice = (NamedDevice) object; + if (object instanceof final NamedDevice namedDevice) { this.typeNames.addAll(namedDevice.getDeviceTypeNames()); } + if (this.typeNames.isEmpty()) { + this.typeNames.add(toNiceTypeName(object.getClass())); + } } /** @@ -83,10 +90,31 @@ public List getTypeNames() { } @Override - public List getMethods() { + public List getMethodGroups() { return methods; } + @Override + public void mount() { + if (object instanceof LifecycleAwareDevice device) { + device.onDeviceMounted(); + } + } + + @Override + public void unmount() { + if (object instanceof LifecycleAwareDevice device) { + device.onDeviceUnmounted(); + } + } + + @Override + public void dispose() { + if (object instanceof LifecycleAwareDevice device) { + device.onDeviceDisposed(); + } + } + @Override public boolean equals(@Nullable final Object o) { if (this == o) return true; @@ -104,4 +132,16 @@ public int hashCode() { public String toString() { return className; } + + /////////////////////////////////////////////////////////////////// + + private static String toNiceTypeName(final Class deviceClass) { + final String name = deviceClass.getSimpleName() + .replaceFirst("VMDevice$", "") + .replaceFirst("RPCDevice$", "") + .replaceFirst("Device$", ""); + return name + .replaceAll("([a-z])([A-Z])", "$1_$2") + .toLowerCase(Locale.ROOT); + } } diff --git a/src/main/java/li/cil/oc2/api/bus/device/object/Parameter.java b/src/main/java/li/cil/oc2/api/bus/device/object/Parameter.java index 6ac838e95..862264986 100644 --- a/src/main/java/li/cil/oc2/api/bus/device/object/Parameter.java +++ b/src/main/java/li/cil/oc2/api/bus/device/object/Parameter.java @@ -1,3 +1,5 @@ +/* SPDX-License-Identifier: MIT */ + package li.cil.oc2.api.bus.device.object; import java.lang.annotation.ElementType; diff --git a/src/main/java/li/cil/oc2/api/bus/device/object/package-info.java b/src/main/java/li/cil/oc2/api/bus/device/object/package-info.java index 31c0b1450..261c7339a 100644 --- a/src/main/java/li/cil/oc2/api/bus/device/object/package-info.java +++ b/src/main/java/li/cil/oc2/api/bus/device/object/package-info.java @@ -1,7 +1,9 @@ +/* SPDX-License-Identifier: MIT */ + @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault package li.cil.oc2.api.bus.device.object; -import mcp.MethodsReturnNonnullByDefault; +import net.minecraft.MethodsReturnNonnullByDefault; -import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/src/main/java/li/cil/oc2/api/bus/device/package-info.java b/src/main/java/li/cil/oc2/api/bus/device/package-info.java index 78c013ec3..609af817b 100644 --- a/src/main/java/li/cil/oc2/api/bus/device/package-info.java +++ b/src/main/java/li/cil/oc2/api/bus/device/package-info.java @@ -1,47 +1,9 @@ -/** - * The device bus is the glue that connects devices and VMs. - *

- * A bus must always be managed by a {@link li.cil.oc2.api.bus.DeviceBusController}. - * If there is no controller, there is no (connected) bus. - *

- * When a controller performs a scan, it collects a list of connected - * {@link li.cil.oc2.api.bus.DeviceBusElement}s thus defining a - * {@link li.cil.oc2.api.bus.DeviceBus}. - * How the controller scans for elements depends on the implementation. - * One example is a block-based controller which scans adjacent blocks - * in a recursive manner -- usually up to some maximum bus complexity. - *

- * {@link li.cil.oc2.api.bus.DeviceBusElement}s are responsible for - * providing a list of devices connected to them. Whether they play an - * active role and seek out devices, or passively expect devices to be - * registered with them depends on the implementation implementation. - *

- * After a scan {@link li.cil.oc2.api.bus.DeviceBusController}s then - * collect all devices from all bus elements to build a global set - * of devices on the bus. - *

- * There can be various types of devices on a bus, but which types are - * supported will depend on the context of the controller. Currently two - * types of devices are are defined in this API, {@link li.cil.oc2.api.bus.device.rpc.RPCDevice} - * and {@link li.cil.oc2.api.bus.device.vm.VMDevice}. - *

    - *
  • - * RPC devices are a high-level system for providing VMs with means of - * calling methods on such devices. The protocol used allows VMs to - * dynamically detect what devices are present and what methods may be - * called on them. - *
  • - *
  • - * VM devices are low-level devices that may register emulations of actual - * devices with a VM. Such devices will require drivers to be present - * inside the VM to work. - *
  • - *
- */ +/* SPDX-License-Identifier: MIT */ + @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault package li.cil.oc2.api.bus.device; -import mcp.MethodsReturnNonnullByDefault; +import net.minecraft.MethodsReturnNonnullByDefault; -import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/src/main/java/li/cil/oc2/api/bus/device/provider/BlockDeviceProvider.java b/src/main/java/li/cil/oc2/api/bus/device/provider/BlockDeviceProvider.java index 6a80e7c35..247d3ff87 100644 --- a/src/main/java/li/cil/oc2/api/bus/device/provider/BlockDeviceProvider.java +++ b/src/main/java/li/cil/oc2/api/bus/device/provider/BlockDeviceProvider.java @@ -1,7 +1,12 @@ +/* SPDX-License-Identifier: MIT */ + package li.cil.oc2.api.bus.device.provider; import li.cil.oc2.api.bus.device.Device; -import net.minecraftforge.common.util.LazyOptional; +import li.cil.oc2.api.bus.device.rpc.RPCDevice; +import li.cil.oc2.api.bus.device.vm.VMDevice; +import li.cil.oc2.api.util.Invalidatable; +import net.minecraft.nbt.CompoundTag; import net.minecraftforge.registries.IForgeRegistryEntry; /** @@ -25,7 +30,7 @@ * are registered. For example: *
  * class YourModInitialization {
- *     static DeferredRegister<BlockDeviceProvider> BLOCK_DEVICE_PROVIDERS = DeferredRegister.create(BlockDeviceProvider.class, "your_mod_id");
+ *     static DeferredRegister<BlockDeviceProvider> BLOCK_DEVICE_PROVIDERS = DeferredRegister.create(BlockDeviceProvider.REGISTRY, "your_mod_id");
  *
  *     static void initialize() {
  *         BLOCK_DEVICE_PROVIDERS.register("your_block_device_name", YourBlockDeviceProvider::new);
@@ -47,5 +52,25 @@ public interface BlockDeviceProvider extends IForgeRegistryEntry getDevice(BlockDeviceQuery query);
+    Invalidatable getDevice(BlockDeviceQuery query);
+
+    /**
+     * Last-resort cleanup method for devices provided by this provider.
+     * 

+ * This is the equivalent of {@link RPCDevice#dispose()} or {@link VMDevice#dispose()}, + * for devices that have gone missing unexpectedly, so this method could no longer be + * called on the actual device. + *

+ * For block devices, this can happen if the block the device was created for has been + * removed while the connected computer was unloaded, or the cable connecting the block + * the device was provided for with the computer was broken while the computer was unloaded. + *

+ * Implementing this is only necessary, if the device holds some out-of-NBT serialized + * data, or does something similar. + * + * @param query the query that resulted in a missing device being detected. + * @param tag data last serialized by the device that went missing. + */ + default void unmount(final BlockDeviceQuery query, final CompoundTag tag) { + } } diff --git a/src/main/java/li/cil/oc2/api/bus/device/provider/BlockDeviceQuery.java b/src/main/java/li/cil/oc2/api/bus/device/provider/BlockDeviceQuery.java index 6fe74438b..5781523ab 100644 --- a/src/main/java/li/cil/oc2/api/bus/device/provider/BlockDeviceQuery.java +++ b/src/main/java/li/cil/oc2/api/bus/device/provider/BlockDeviceQuery.java @@ -1,8 +1,11 @@ +/* SPDX-License-Identifier: MIT */ + package li.cil.oc2.api.bus.device.provider; -import net.minecraft.util.Direction; -import net.minecraft.util.math.BlockPos; -import net.minecraft.world.World; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.block.entity.BlockEntity; import javax.annotation.Nullable; @@ -13,11 +16,11 @@ */ public interface BlockDeviceQuery { /** - * The world containing the block this query is performed for. + * The level containing the block this query is performed for. * - * @return the world containing the block. + * @return the level containing the block. */ - World getLevel(); + LevelAccessor getLevel(); /** * The position of the block this query is performed for. @@ -27,9 +30,9 @@ public interface BlockDeviceQuery { BlockPos getQueryPosition(); /** - * The side of the block this query is performed on, if any. + * The world-space side of the block this query is performed on, if any. *

- * May be {@code null} just as when requesting a capability from a tile entity. + * May be {@code null} just as when requesting a capability from a {@link BlockEntity}. * * @return the side of the block. */ diff --git a/src/main/java/li/cil/oc2/api/bus/device/provider/ItemDeviceProvider.java b/src/main/java/li/cil/oc2/api/bus/device/provider/ItemDeviceProvider.java index a3156ba92..2608c5358 100644 --- a/src/main/java/li/cil/oc2/api/bus/device/provider/ItemDeviceProvider.java +++ b/src/main/java/li/cil/oc2/api/bus/device/provider/ItemDeviceProvider.java @@ -1,10 +1,14 @@ +/* SPDX-License-Identifier: MIT */ + package li.cil.oc2.api.bus.device.provider; -import li.cil.oc2.api.bus.device.DeviceType; -import li.cil.oc2.api.bus.device.DeviceTypes; import li.cil.oc2.api.bus.device.ItemDevice; +import li.cil.oc2.api.bus.device.rpc.RPCDevice; +import li.cil.oc2.api.bus.device.vm.VMDevice; +import net.minecraft.nbt.CompoundTag; import net.minecraftforge.registries.IForgeRegistryEntry; +import javax.annotation.Nullable; import java.util.Optional; /** @@ -23,7 +27,7 @@ * are registered. For example: *

  * class YourModInitialization {
- *     static DeferredRegister<ItemDeviceProvider> ITEM_DEVICE_PROVIDERS = DeferredRegister.create(ItemDeviceProvider.class, "your_mod_id");
+ *     static DeferredRegister<ItemDeviceProvider> ITEM_DEVICE_PROVIDERS = DeferredRegister.create(ItemDeviceProvider.REGISTRY, "your_mod_id");
  *
  *     static void initialize() {
  *         ITEM_DEVICE_PROVIDERS.register("your_item_device_name", YourItemDeviceProvider::new);
@@ -47,18 +51,6 @@ public interface ItemDeviceProvider extends IForgeRegistryEntry getDevice(ItemDeviceQuery query);
 
-    /**
-     * Get the type of a device that would be obtained from {@link #getDevice(ItemDeviceQuery)}
-     * if called with the same query. The device type controls which slot devices may be
-     * inserted in in item device containers.
-     *
-     * @param query the query describing the object to get the {@link DeviceType} for.
-     * @return the device type for the specified type, if available.
-     */
-    default Optional getDeviceType(final ItemDeviceQuery query) {
-        return Optional.of(DeviceTypes.CARD);
-    }
-
     /**
      * The amount of energy the device that would be returned by {@link #getDevice(ItemDeviceQuery)}
      * will consume per tick while the VM using it is running.
@@ -71,4 +63,23 @@ default Optional getDeviceType(final ItemDeviceQuery query) {
     default int getEnergyConsumption(final ItemDeviceQuery query) {
         return 0;
     }
+
+    /**
+     * Last-resort cleanup method for devices provided by this provider.
+     * 

+ * This is the equivalent of {@link RPCDevice#dispose()} or {@link VMDevice#dispose()}, + * for devices that have gone missing unexpectedly, so this method could no longer be + * called on the actual device. + *

+ * For item devices this is rather unlikely. It means an item disappeared while the + * block managing the item device was unloaded. + *

+ * Implementing this is only necessary, if the device holds some out-of-NBT serialized + * data, or does something similar. + * + * @param query the query that resulted in a missing device being detected, if available. + * @param tag the data last serialized by the device went missing. + */ + default void unmount(@Nullable final ItemDeviceQuery query, final CompoundTag tag) { + } } diff --git a/src/main/java/li/cil/oc2/api/bus/device/provider/ItemDeviceQuery.java b/src/main/java/li/cil/oc2/api/bus/device/provider/ItemDeviceQuery.java index 709247222..9bed31a55 100644 --- a/src/main/java/li/cil/oc2/api/bus/device/provider/ItemDeviceQuery.java +++ b/src/main/java/li/cil/oc2/api/bus/device/provider/ItemDeviceQuery.java @@ -1,8 +1,10 @@ +/* SPDX-License-Identifier: MIT */ + package li.cil.oc2.api.bus.device.provider; -import net.minecraft.entity.Entity; -import net.minecraft.item.ItemStack; -import net.minecraft.tileentity.TileEntity; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.entity.BlockEntity; import java.util.Optional; @@ -13,11 +15,11 @@ */ public interface ItemDeviceQuery { /** - * The {@link TileEntity} that holds the item this query is for. + * The {@link BlockEntity} that holds the item this query is for. * - * @return the {@link TileEntity} hosting the device, if any. + * @return the {@link BlockEntity} hosting the device, if any. */ - Optional getContainerTileEntity(); + Optional getContainerBlockEntity(); /** * The {@link Entity} that holds the item this query is for. diff --git a/src/main/java/li/cil/oc2/api/bus/device/provider/package-info.java b/src/main/java/li/cil/oc2/api/bus/device/provider/package-info.java index fbf7c506d..fc586bd3b 100644 --- a/src/main/java/li/cil/oc2/api/bus/device/provider/package-info.java +++ b/src/main/java/li/cil/oc2/api/bus/device/provider/package-info.java @@ -1,7 +1,9 @@ +/* SPDX-License-Identifier: MIT */ + @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault package li.cil.oc2.api.bus.device.provider; -import mcp.MethodsReturnNonnullByDefault; +import net.minecraft.MethodsReturnNonnullByDefault; -import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/src/main/java/li/cil/oc2/api/bus/device/rpc/AbstractRPCMethod.java b/src/main/java/li/cil/oc2/api/bus/device/rpc/AbstractRPCMethod.java index 921806297..c6397ad81 100644 --- a/src/main/java/li/cil/oc2/api/bus/device/rpc/AbstractRPCMethod.java +++ b/src/main/java/li/cil/oc2/api/bus/device/rpc/AbstractRPCMethod.java @@ -1,5 +1,9 @@ +/* SPDX-License-Identifier: MIT */ + package li.cil.oc2.api.bus.device.rpc; +import javax.annotation.Nullable; + /** * Convenience base class for {@link RPCMethod} implementations. */ @@ -30,6 +34,8 @@ protected AbstractRPCMethod(final String name, final RPCParameter... parameters) this(name, false, void.class, parameters); } + /////////////////////////////////////////////////////////////////// + @Override public String getName() { return name; @@ -49,4 +55,14 @@ public Class getReturnType() { public RPCParameter[] getParameters() { return parameters; } + + @Nullable + public Object invoke(final RPCInvocation invocation) throws Throwable { + return invoke(invocation.tryDeserializeParameters(getParameters()).orElseThrow(IllegalArgumentException::new)); + } + + /////////////////////////////////////////////////////////////////// + + @Nullable + protected abstract Object invoke(final Object... parameters) throws Throwable; } diff --git a/src/main/java/li/cil/oc2/api/bus/device/rpc/RPCDevice.java b/src/main/java/li/cil/oc2/api/bus/device/rpc/RPCDevice.java index cc5a35fd0..68942d144 100644 --- a/src/main/java/li/cil/oc2/api/bus/device/rpc/RPCDevice.java +++ b/src/main/java/li/cil/oc2/api/bus/device/rpc/RPCDevice.java @@ -1,5 +1,8 @@ +/* SPDX-License-Identifier: MIT */ + package li.cil.oc2.api.bus.device.rpc; +import li.cil.oc2.api.bus.DeviceBus; import li.cil.oc2.api.bus.device.Device; import li.cil.oc2.api.bus.device.object.ObjectDevice; @@ -9,14 +12,39 @@ * Provides an interface for an RPC device, describing the methods that can be * called on it and the type names it can be detected by/is compatible with. *

- * A {@link RPCDevice} may represent a single view onto some device or be a + * A {@link RPCDevice} may represent a single view onto some device, or be a * collection of multiple aggregated {@link RPCDevice}s. One underlying device - * may have multiple {@link RPCDevice}s providing different methods for the - * device. This allows specifying general purpose interfaces which provide logic - * for some aspect of an underlying device which may be shared with other devices. + * may have multiple {@link RPCDevice}s, providing different methods for the + * device. This allows specifying general purpose interfaces, which provide logic + * for some aspect of an underlying device, which may be shared with other devices. *

- * The easiest and hence recommended way of implementing this interface is to use + * The easiest, and hence recommended, way of implementing this interface, is to use * the {@link ObjectDevice} class. + *

+ * The lifecycle for {@link RPCDevice}s is as follows: + *

+ * ┌──────────────┐ ┌────────────────┐
+ * │serializeNBT()│ │deserializeNBT()◄───────┐
+ * └──────────────┘ └───────┬────────┘       │
+ *   May be called          │VM starts or    │
+ *   at any time,    ┌──────┤resumes after   │
+ *   except while    │      │load            │
+ *   unloaded...     │  ┌───▼───┐            │
+ *                   │  │mount()│            │
+ *                   │  └───┬───┘            │Chunk
+ *                   │      │VM stops or     │unloaded
+ *                   │      │is unloaded     │
+ *                   │      │                │
+ *                   │ ┌────▼────┐           │
+ *                   │ │unmount()├───────────┤
+ *                   │ └────┬────┘           │
+ *                   │      │VM stopped or   │
+ *                   │      │device removed  │
+ *                   │      │                │
+ *                   │ ┌────▼────┐           │
+ *                   └─┤dispose()├───────────┘
+ *                     └─────────┘
+ * 
* * @see ObjectDevice * @see li.cil.oc2.api.bus.device.provider.BlockDeviceProvider @@ -38,20 +66,32 @@ public interface RPCDevice extends Device { List getTypeNames(); /** - * The list of methods provided by this interface. + * The list of methods groups provided by this interface. * - * @return the list of methods. + * @return the list of method groups. + */ + List getMethodGroups(); + + /** + * Called to start this device. + *

+ * This is called when the connected virtual machine starts, or when the device + * is added to a {@link DeviceBus} with a currently running virtual machine. */ - List getMethods(); + default void mount() { + } /** - * Called when the device is suspended. + * Called to pause this device. + *

+ * Called when the connected virtual machine is suspended (chunk unload/server stopped/...). *

- * This can happen when the world area containing the context the device was loaded in is unloaded, - * e.g. due to player moving too far away from the area. + * Also called when the connected virtual machine stops or the device is removed from a + * {@link DeviceBus} with a currently running virtual machine. In this case, {@link #dispose()} + * will be called after this method returns. *

- * Intended for soft-releasing unmanaged resource, i.e. non-persisted unmanaged resources. + * If {@link #mount()} was called, this is guaranteed to be called. */ - default void suspend() { + default void unmount() { } } diff --git a/src/main/java/li/cil/oc2/api/bus/device/rpc/RPCInvocation.java b/src/main/java/li/cil/oc2/api/bus/device/rpc/RPCInvocation.java new file mode 100644 index 000000000..e4c0c6f95 --- /dev/null +++ b/src/main/java/li/cil/oc2/api/bus/device/rpc/RPCInvocation.java @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: MIT */ + +package li.cil.oc2.api.bus.device.rpc; + +import com.google.gson.Gson; +import com.google.gson.JsonArray; + +import java.util.Optional; + +/** + * Describes an invocation context for an {@link RPCMethod}. + *

+ * This is used to both query {@link RPCMethodGroup}s for matching overloads, as + * well as for invoking matched {@link RPCMethod}s. + */ +public interface RPCInvocation { + /** + * The raw parameter list of this invocation. + * + * @return the parameter list. + */ + JsonArray getParameters(); + + /** + * The serialization context that may be used to deserialize parameters. + * + * @return the serialization context. + */ + Gson getGson(); + + /** + * Utility method for deserializing parameters, given a list of parameter types. + *

+ * This method softly fails by returning {@link Optional#empty()} if: + *

    + *
  • The length of the parameters in this invocation and the length of the parameter types do not match.
  • + *
  • Any one of the parameters cannot be deserialized into the required type.
  • + *
+ * + * @param parameterTypes the parameter types to deserialize into. + * @return the deserialized parameters, if possible. + */ + Optional tryDeserializeParameters(RPCParameter... parameterTypes); +} diff --git a/src/main/java/li/cil/oc2/api/bus/device/rpc/RPCMethod.java b/src/main/java/li/cil/oc2/api/bus/device/rpc/RPCMethod.java index 8b0f2de51..d675451a0 100644 --- a/src/main/java/li/cil/oc2/api/bus/device/rpc/RPCMethod.java +++ b/src/main/java/li/cil/oc2/api/bus/device/rpc/RPCMethod.java @@ -1,10 +1,13 @@ +/* SPDX-License-Identifier: MIT */ + package li.cil.oc2.api.bus.device.rpc; -import li.cil.oc2.api.bus.DeviceBusController; import li.cil.oc2.api.bus.device.object.ObjectDevice; import javax.annotation.Nullable; +import java.util.Collections; import java.util.Optional; +import java.util.Set; /** * Represents a single method that can be exposed by a {@link RPCDevice}. @@ -18,18 +21,7 @@ * * @see ObjectDevice */ -public interface RPCMethod { - /** - * The name of the method. - *

- * When invoked through a {@link DeviceBusController} this is what the method - * will be referenced by, so the name should be unlikely to be duplicated in - * another device to avoid ambiguity when devices are combined. - * - * @return the name of the method. - */ - String getName(); - +public interface RPCMethod extends RPCMethodGroup { /** * When {@code true}, invocations of this method will be synchronized to the main thread. * @@ -54,23 +46,19 @@ public interface RPCMethod { /** * Called to run this method. *

- * Implementations should expect the passed {@code parameters} to match the - * declared parameters returned by {@link #getParameters()}. If the parameters - * do not match, and exception should be raised. - *

* Important: methods are expected to not irrevocably corrupt internal * state, even when they throw an exception. As such, implementations should * perform internal error handling to prevent state corruption and only throw * exceptions to communicate that an error happened during the invocation. * - * @param parameters the parameters for the method. + * @param invocation the invocation information. * @return the return value, or {@code null} if none. * @throws Throwable if the parameters did not match or something inside the * method caused an exception. The caller is responsible for * catching these and passing them on appropriately. */ @Nullable - Object invoke(Object... parameters) throws Throwable; + Object invoke(final RPCInvocation invocation) throws Throwable; /** * An optional description of the method. @@ -93,4 +81,14 @@ default Optional getDescription() { default Optional getReturnValueDescription() { return Optional.empty(); } + + @Override + default Set getOverloads() { + return Collections.singleton(this); + } + + @Override + default Optional findOverload(final RPCInvocation invocation) { + return invocation.tryDeserializeParameters(getParameters()).map(parameters -> this); + } } diff --git a/src/main/java/li/cil/oc2/api/bus/device/rpc/RPCMethodGroup.java b/src/main/java/li/cil/oc2/api/bus/device/rpc/RPCMethodGroup.java new file mode 100644 index 000000000..74cf93ccc --- /dev/null +++ b/src/main/java/li/cil/oc2/api/bus/device/rpc/RPCMethodGroup.java @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: MIT */ + +package li.cil.oc2.api.bus.device.rpc; + +import li.cil.oc2.api.bus.DeviceBusController; + +import java.util.Collections; +import java.util.Optional; +import java.util.Set; + +/** + * A group of overloaded {@link RPCMethod}s, i.e. methods with the same name but different signatures. + */ +public interface RPCMethodGroup { + /** + * The name of the method group/of the grouped {@link RPCMethod}s. + *

+ * When invoked through a {@link DeviceBusController}, this is what the method group + * will be referenced by, so the name should be unlikely to be duplicated in another + * device, to avoid ambiguity when devices are combined. + * + * @return the name of the method group. + */ + String getName(); + + /** + * The list of overloads in this method group, if available. + *

+ * This is used when the virtual machine queries the method descriptions for some device, usually + * to display it to the user as documentation. + *

+ * This may return an empty set, in which case the group will present itself with just its name. + * + * @return the set of {@link RPCMethod}s in this method group. + */ + default Set getOverloads() { + return Collections.emptySet(); + } + + /** + * Attempts to find an overload in this method group that matches the provided, serialized parameters. + * + * @param invocation the invocation information. + * @return a matching method overload, if possible. + */ + Optional findOverload(RPCInvocation invocation); +} diff --git a/src/main/java/li/cil/oc2/api/bus/device/rpc/RPCParameter.java b/src/main/java/li/cil/oc2/api/bus/device/rpc/RPCParameter.java index e0fc58610..51f9ecff3 100644 --- a/src/main/java/li/cil/oc2/api/bus/device/rpc/RPCParameter.java +++ b/src/main/java/li/cil/oc2/api/bus/device/rpc/RPCParameter.java @@ -1,6 +1,6 @@ -package li.cil.oc2.api.bus.device.rpc; +/* SPDX-License-Identifier: MIT */ -import li.cil.oc2.api.bus.DeviceBusController; +package li.cil.oc2.api.bus.device.rpc; import java.util.Optional; @@ -11,8 +11,10 @@ public interface RPCParameter { /** * The type of this parameter. *

- * This is used by {@link DeviceBusController}s to convert parameters from a lower - * level representation before passing it to {@link RPCMethod#invoke(Object...)}. + * May be used inside VMs to generate documentation. + *

+ * This is used by {@link AbstractRPCMethod}s to convert parameters from a lower + * level representation before passing it to {@link AbstractRPCMethod#invoke(Object...)}. * As such, the types used must be kept simple. As a rule of thumb, only primitives * and POJOs should be used. * diff --git a/src/main/java/li/cil/oc2/api/bus/device/rpc/package-info.java b/src/main/java/li/cil/oc2/api/bus/device/rpc/package-info.java index b3bef420e..c3668cf11 100644 --- a/src/main/java/li/cil/oc2/api/bus/device/rpc/package-info.java +++ b/src/main/java/li/cil/oc2/api/bus/device/rpc/package-info.java @@ -1,7 +1,9 @@ +/* SPDX-License-Identifier: MIT */ + @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault package li.cil.oc2.api.bus.device.rpc; -import mcp.MethodsReturnNonnullByDefault; +import net.minecraft.MethodsReturnNonnullByDefault; -import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/src/main/java/li/cil/oc2/api/bus/device/vm/FirmwareLoader.java b/src/main/java/li/cil/oc2/api/bus/device/vm/FirmwareLoader.java index cf92b914a..f93a4cd43 100644 --- a/src/main/java/li/cil/oc2/api/bus/device/vm/FirmwareLoader.java +++ b/src/main/java/li/cil/oc2/api/bus/device/vm/FirmwareLoader.java @@ -1,3 +1,5 @@ +/* SPDX-License-Identifier: MIT */ + package li.cil.oc2.api.bus.device.vm; /** @@ -6,5 +8,4 @@ * It is used exclusively to check if some firmware will be loaded early in the * startup process, to provide a useful error to the user if none is present. */ -public interface FirmwareLoader extends VMDevice { -} +public interface FirmwareLoader extends VMDevice { } diff --git a/src/main/java/li/cil/oc2/api/bus/device/vm/VMDevice.java b/src/main/java/li/cil/oc2/api/bus/device/vm/VMDevice.java index 5574a6717..a65748197 100644 --- a/src/main/java/li/cil/oc2/api/bus/device/vm/VMDevice.java +++ b/src/main/java/li/cil/oc2/api/bus/device/vm/VMDevice.java @@ -1,5 +1,8 @@ +/* SPDX-License-Identifier: MIT */ + package li.cil.oc2.api.bus.device.vm; +import li.cil.oc2.api.bus.DeviceBus; import li.cil.oc2.api.bus.device.Device; import li.cil.oc2.api.bus.device.rpc.RPCDevice; import li.cil.oc2.api.bus.device.vm.context.InterruptAllocator; @@ -16,13 +19,43 @@ *

* To listen to lifecycle events of the VM and the device, register to the event * bus provided via {@link VMContext#getEventBus()} in {@link #mount(VMContext)}. + *

+ * The lifecycle for VMDevices can be depicted as such: + *

+ * ┌──────────────┐ ┌────────────────┐
+ * │serializeNBT()│ │deserializeNBT()◄───────┐
+ * └──────────────┘ └───────┬────────┘       │
+ *   May be called          │VM starts or    │
+ *   at any time,    ┌──────┤resumes after   │
+ *   except while    │      │load            │
+ *   unloaded...     │  ┌───▼───┐            │
+ *                   │  │mount()│            │
+ *                   │  └───┬───┘            │Chunk
+ *                   │      │VM stops or     │unloaded
+ *                   │      │is unloaded     │
+ *                   │      │                │
+ *                   │ ┌────▼────┐           │
+ *                   │ │unmount()├───────────┤
+ *                   │ └────┬────┘           │
+ *                   │      │VM stopped or   │
+ *                   │      │device removed  │
+ *                   │      │                │
+ *                   │ ┌────▼────┐           │
+ *                   └─┤dispose()├───────────┘
+ *                     └─────────┘
+ * 
+ * Note that if any other {@link VMDevice} fails mounting, all mounted devices + * will immediately unmounted and disposed. * * @see li.cil.oc2.api.bus.device.provider.BlockDeviceProvider * @see li.cil.oc2.api.bus.device.provider.ItemDeviceProvider */ public interface VMDevice extends Device { /** - * Called to initialize this device. + * Called to start this device. + *

+ * This is called when the connected virtual machine starts, or when the device + * is added to a {@link DeviceBus} with a currently running virtual machine. *

* Register {@link MemoryMappedDevice}s and claim interrupts via the * {@link InterruptAllocator} made available through the {@code context}. @@ -30,7 +63,7 @@ public interface VMDevice extends Device { * If loading cannot complete, e.g. because resources cannot be allocated, * this should return {@code false}. The virtual machine will periodically * try again to load failed devices. The virtual machine will only start - * running after all devices have successfully loaded. + * or resume after all devices have successfully loaded. * * @param context the virtual machine context. * @return {@code true} if the device was loaded successfully; {@code false} otherwise. @@ -38,23 +71,13 @@ public interface VMDevice extends Device { VMDeviceLoadResult mount(VMContext context); /** - * Called when the device is removed from the context it was loaded with. + * Called to pause this device. *

- * This can happen because the VM was stopped or the device was removed from - * the device bus that connected it to the VM, for example. + * Called when the connected virtual machine is suspended (chunk unload/server stopped/...). *

- * Intended for releasing resources acquired in {@link #mount(VMContext)}. + * Also called when the connected virtual machine stops or the device is removed from a + * {@link DeviceBus} with a currently running virtual machine. In this case, {@link #dispose()} + * will be called after this method returns. */ void unmount(); - - /** - * Called when the device is suspended. - *

- * This can happen when the world area containing the context the device was loaded in is unloaded, - * e.g. due to player moving too far away from the area. - *

- * Intended for soft-releasing resources acquired in {@link #mount(VMContext)}, i.e. non-persisted - * unmanaged resources. - */ - void suspend(); } diff --git a/src/main/java/li/cil/oc2/api/bus/device/vm/VMDeviceLoadResult.java b/src/main/java/li/cil/oc2/api/bus/device/vm/VMDeviceLoadResult.java index ab2775f2d..f8cf523cf 100644 --- a/src/main/java/li/cil/oc2/api/bus/device/vm/VMDeviceLoadResult.java +++ b/src/main/java/li/cil/oc2/api/bus/device/vm/VMDeviceLoadResult.java @@ -1,7 +1,9 @@ +/* SPDX-License-Identifier: MIT */ + package li.cil.oc2.api.bus.device.vm; import li.cil.oc2.api.bus.device.vm.context.VMContext; -import net.minecraft.util.text.ITextComponent; +import net.minecraft.network.chat.Component; import javax.annotation.Nullable; @@ -28,7 +30,7 @@ public static VMDeviceLoadResult fail() { } private final boolean wasSuccessful; - @Nullable private ITextComponent message; + @Nullable private Component message; private VMDeviceLoadResult(final boolean wasSuccessful) { this.wasSuccessful = wasSuccessful; @@ -51,7 +53,7 @@ public boolean wasSuccessful() { * @param value the error message. * @return this load result, with the message set to the specified value. */ - public VMDeviceLoadResult withErrorMessage(final ITextComponent value) { + public VMDeviceLoadResult withErrorMessage(final Component value) { message = value; return this; } @@ -62,7 +64,7 @@ public VMDeviceLoadResult withErrorMessage(final ITextComponent value) { * @return the error message. */ @Nullable - public ITextComponent getErrorMessage() { + public Component getErrorMessage() { return message; } } diff --git a/src/main/java/li/cil/oc2/api/bus/device/vm/context/InterruptAllocator.java b/src/main/java/li/cil/oc2/api/bus/device/vm/context/InterruptAllocator.java index 097b9dafa..fb7fb9917 100644 --- a/src/main/java/li/cil/oc2/api/bus/device/vm/context/InterruptAllocator.java +++ b/src/main/java/li/cil/oc2/api/bus/device/vm/context/InterruptAllocator.java @@ -1,3 +1,5 @@ +/* SPDX-License-Identifier: MIT */ + package li.cil.oc2.api.bus.device.vm.context; import li.cil.oc2.api.bus.device.vm.VMDevice; diff --git a/src/main/java/li/cil/oc2/api/bus/device/vm/context/MemoryAllocator.java b/src/main/java/li/cil/oc2/api/bus/device/vm/context/MemoryAllocator.java index daa4fa65c..084e1847c 100644 --- a/src/main/java/li/cil/oc2/api/bus/device/vm/context/MemoryAllocator.java +++ b/src/main/java/li/cil/oc2/api/bus/device/vm/context/MemoryAllocator.java @@ -1,3 +1,5 @@ +/* SPDX-License-Identifier: MIT */ + package li.cil.oc2.api.bus.device.vm.context; /** diff --git a/src/main/java/li/cil/oc2/api/bus/device/vm/context/MemoryRangeAllocator.java b/src/main/java/li/cil/oc2/api/bus/device/vm/context/MemoryRangeAllocator.java index 9fac07335..f148d6aa0 100644 --- a/src/main/java/li/cil/oc2/api/bus/device/vm/context/MemoryRangeAllocator.java +++ b/src/main/java/li/cil/oc2/api/bus/device/vm/context/MemoryRangeAllocator.java @@ -1,3 +1,5 @@ +/* SPDX-License-Identifier: MIT */ + package li.cil.oc2.api.bus.device.vm.context; import li.cil.oc2.api.bus.device.vm.VMDevice; diff --git a/src/main/java/li/cil/oc2/api/bus/device/vm/context/VMContext.java b/src/main/java/li/cil/oc2/api/bus/device/vm/context/VMContext.java index d562dbf5c..e3090cea5 100644 --- a/src/main/java/li/cil/oc2/api/bus/device/vm/context/VMContext.java +++ b/src/main/java/li/cil/oc2/api/bus/device/vm/context/VMContext.java @@ -1,3 +1,5 @@ +/* SPDX-License-Identifier: MIT */ + package li.cil.oc2.api.bus.device.vm.context; import li.cil.oc2.api.bus.DeviceBus; @@ -93,17 +95,4 @@ public interface VMContext { * @return the event bus. */ VMLifecycleEventBus getEventBus(); - - /** - * Waits for the executor thread of the virtual machine to finish running. - *

- * Events subscribers can only be registered inside {@link VMDevice#mount(VMContext)}. - * Trying to register subscribers after that method has returned will result in an - * exception. - *

- * Note that this may trigger a {@link li.cil.oc2.api.bus.device.vm.event.VMPausingEvent} - * if the virtual machine has not been paused before. Calling this on a paused virtual - * machine is a no-op. - */ - void joinWorkerThread(); } diff --git a/src/main/java/li/cil/oc2/api/bus/device/vm/context/VMLifecycleEventBus.java b/src/main/java/li/cil/oc2/api/bus/device/vm/context/VMLifecycleEventBus.java index 24f93fea5..de272ddb0 100644 --- a/src/main/java/li/cil/oc2/api/bus/device/vm/context/VMLifecycleEventBus.java +++ b/src/main/java/li/cil/oc2/api/bus/device/vm/context/VMLifecycleEventBus.java @@ -1,7 +1,13 @@ +/* SPDX-License-Identifier: MIT */ + package li.cil.oc2.api.bus.device.vm.context; /** - * Allows registering for {@link li.cil.oc2.api.bus.device.vm.event.VMLifecycleEvent}s. + * Allows registering for VM lifecycle events. + * + * @see li.cil.oc2.api.bus.device.vm.event.VMInitializingEvent + * @see li.cil.oc2.api.bus.device.vm.event.VMSynchronizeEvent + * @see li.cil.oc2.api.bus.device.vm.event.VMResumedRunningEvent */ public interface VMLifecycleEventBus { /** diff --git a/src/main/java/li/cil/oc2/api/bus/device/vm/context/package-info.java b/src/main/java/li/cil/oc2/api/bus/device/vm/context/package-info.java index 23a4eaedc..b67780c08 100644 --- a/src/main/java/li/cil/oc2/api/bus/device/vm/context/package-info.java +++ b/src/main/java/li/cil/oc2/api/bus/device/vm/context/package-info.java @@ -1,7 +1,9 @@ +/* SPDX-License-Identifier: MIT */ + @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault package li.cil.oc2.api.bus.device.vm.context; -import mcp.MethodsReturnNonnullByDefault; +import net.minecraft.MethodsReturnNonnullByDefault; -import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/src/main/java/li/cil/oc2/api/bus/device/vm/event/VMInitializationException.java b/src/main/java/li/cil/oc2/api/bus/device/vm/event/VMInitializationException.java index bbd3ea3ec..fb6c6ee22 100644 --- a/src/main/java/li/cil/oc2/api/bus/device/vm/event/VMInitializationException.java +++ b/src/main/java/li/cil/oc2/api/bus/device/vm/event/VMInitializationException.java @@ -1,6 +1,8 @@ +/* SPDX-License-Identifier: MIT */ + package li.cil.oc2.api.bus.device.vm.event; -import net.minecraft.util.text.ITextComponent; +import net.minecraft.network.chat.Component; import java.util.Optional; @@ -8,11 +10,11 @@ * May be fired by devices while handling {@link VMInitializingEvent} to indicate that initialization failed. */ public final class VMInitializationException extends RuntimeException { - private final ITextComponent message; + private final Component message; /////////////////////////////////////////////////////////////// - public VMInitializationException(final ITextComponent message) { + public VMInitializationException(final Component message) { this.message = message; } @@ -25,11 +27,11 @@ public VMInitializationException() { /** * The error message indicating why initialization failed. *

- * This should be a human readable message, as it may be displayed to the user. + * This should be a human-readable message, as it may be displayed to the user. * * @return the error message. */ - public Optional getErrorMessage() { + public Optional getErrorMessage() { return Optional.ofNullable(message); } } diff --git a/src/main/java/li/cil/oc2/api/bus/device/vm/event/VMInitializingEvent.java b/src/main/java/li/cil/oc2/api/bus/device/vm/event/VMInitializingEvent.java index 48e7b125b..f264abf58 100644 --- a/src/main/java/li/cil/oc2/api/bus/device/vm/event/VMInitializingEvent.java +++ b/src/main/java/li/cil/oc2/api/bus/device/vm/event/VMInitializingEvent.java @@ -1,3 +1,5 @@ +/* SPDX-License-Identifier: MIT */ + package li.cil.oc2.api.bus.device.vm.event; import li.cil.oc2.api.bus.device.vm.VMDevice; @@ -8,8 +10,8 @@ *

* Fired after all devices reported success from {@link VMDevice#mount(VMContext)}. *

- * If a running VM is restored from a saved state, this event will not be fired. It is - * intended for initializing the VM state on boot, e.g. by loading initial executable + * If a running VM is restored from a saved state, this event will not be fired. + * It is intended for initializing the VM state on boot, e.g. by loading initial executable * code into memory. *

* Listeners of this event may throw a {@link VMInitializationException} in case @@ -19,26 +21,4 @@ *

* This is invoked from the worker thread running the VM. */ -public final class VMInitializingEvent { - private final long programStartAddress; - - /////////////////////////////////////////////////////////////// - - public VMInitializingEvent(final long programStartAddress) { - this.programStartAddress = programStartAddress; - } - - /////////////////////////////////////////////////////////////// - - /** - * The address where code execution will begin. - *

- * Some VM implementations may perform some early setup before jumping to this - * memory address. - * - * @return the memory address where code execution begins. - */ - public long getProgramStartAddress() { - return programStartAddress; - } -} +public record VMInitializingEvent(long programStartAddress) { } diff --git a/src/main/java/li/cil/oc2/api/bus/device/vm/event/VMResumedRunningEvent.java b/src/main/java/li/cil/oc2/api/bus/device/vm/event/VMResumedRunningEvent.java index 06b46f0c1..628726ea7 100644 --- a/src/main/java/li/cil/oc2/api/bus/device/vm/event/VMResumedRunningEvent.java +++ b/src/main/java/li/cil/oc2/api/bus/device/vm/event/VMResumedRunningEvent.java @@ -1,14 +1,18 @@ +/* SPDX-License-Identifier: MIT */ + package li.cil.oc2.api.bus.device.vm.event; +import li.cil.oc2.api.bus.device.vm.VMDevice; +import li.cil.oc2.api.bus.device.vm.context.VMContext; + /** - * Fired when the VM resumed running. - *

- * Fired after {@link VMResumingRunningEvent} has been fired and handled by all devices. + * Fired when the VM resumed running, either when first starting up, when resuming after + * being loaded, or after a {@link VMSynchronizeEvent}. *

- * Allows device initialization that relies on all other devices having fully loaded. + * May be used to ensure asynchronous work started in {@link VMDevice#mount(VMContext)} + * completes before regular execution of the virtual machine begins. *

- * Typically this is used in combination with {@link VMPausingEvent}, to re-enable external + * May also be used in combination with {@link VMSynchronizeEvent}, to re-enable external * interactions after VM state is guaranteed to be safe to modify again. */ -public final class VMResumedRunningEvent { -} +public final class VMResumedRunningEvent { } diff --git a/src/main/java/li/cil/oc2/api/bus/device/vm/event/VMResumingRunningEvent.java b/src/main/java/li/cil/oc2/api/bus/device/vm/event/VMResumingRunningEvent.java deleted file mode 100644 index 061bef2f7..000000000 --- a/src/main/java/li/cil/oc2/api/bus/device/vm/event/VMResumingRunningEvent.java +++ /dev/null @@ -1,16 +0,0 @@ -package li.cil.oc2.api.bus.device.vm.event; - -import li.cil.oc2.api.bus.device.vm.VMDevice; -import li.cil.oc2.api.bus.device.vm.context.VMContext; - -/** - * Fired when the VM resumes running. - *

- * Fired after all devices reported success from {@link VMDevice#mount(VMContext)}. - *

- * Fired on initial boot-up as well as when the VM resumes after being restored - * from a saved state as well as when continuing to run after being paused for - * a save. It is intended for awaiting asynchronous load and store operations. - */ -public final class VMResumingRunningEvent { -} diff --git a/src/main/java/li/cil/oc2/api/bus/device/vm/event/VMPausingEvent.java b/src/main/java/li/cil/oc2/api/bus/device/vm/event/VMSynchronizeEvent.java similarity index 53% rename from src/main/java/li/cil/oc2/api/bus/device/vm/event/VMPausingEvent.java rename to src/main/java/li/cil/oc2/api/bus/device/vm/event/VMSynchronizeEvent.java index 6407c1d5a..af0f69b35 100644 --- a/src/main/java/li/cil/oc2/api/bus/device/vm/event/VMPausingEvent.java +++ b/src/main/java/li/cil/oc2/api/bus/device/vm/event/VMSynchronizeEvent.java @@ -1,3 +1,5 @@ +/* SPDX-License-Identifier: MIT */ + package li.cil.oc2.api.bus.device.vm.event; /** @@ -7,6 +9,10 @@ * such interactions until {@link VMResumedRunningEvent} is fired. This is required * if such interactions may modify VM state, to prevent corrupting data being * serialized asynchronously. + *

+ * Note that this is called right before the worker thread used to execute the + * virtual machine is joined to the main thread. As such, only devices that run + * their own threads modifying observable state will need to synchronize these + * threads here. */ -public final class VMPausingEvent { -} +public final class VMSynchronizeEvent { } diff --git a/src/main/java/li/cil/oc2/api/bus/device/vm/event/package-info.java b/src/main/java/li/cil/oc2/api/bus/device/vm/event/package-info.java index 6142ef42f..08173178b 100644 --- a/src/main/java/li/cil/oc2/api/bus/device/vm/event/package-info.java +++ b/src/main/java/li/cil/oc2/api/bus/device/vm/event/package-info.java @@ -1,7 +1,9 @@ +/* SPDX-License-Identifier: MIT */ + @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault package li.cil.oc2.api.bus.device.vm.event; -import mcp.MethodsReturnNonnullByDefault; +import net.minecraft.MethodsReturnNonnullByDefault; -import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/src/main/java/li/cil/oc2/api/bus/device/vm/package-info.java b/src/main/java/li/cil/oc2/api/bus/device/vm/package-info.java index cc5c75b19..6f1005868 100644 --- a/src/main/java/li/cil/oc2/api/bus/device/vm/package-info.java +++ b/src/main/java/li/cil/oc2/api/bus/device/vm/package-info.java @@ -1,7 +1,9 @@ +/* SPDX-License-Identifier: MIT */ + @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault package li.cil.oc2.api.bus.device.vm; -import mcp.MethodsReturnNonnullByDefault; +import net.minecraft.MethodsReturnNonnullByDefault; -import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/src/main/java/li/cil/oc2/api/bus/package-info.java b/src/main/java/li/cil/oc2/api/bus/package-info.java index 19591f096..89ba550b9 100644 --- a/src/main/java/li/cil/oc2/api/bus/package-info.java +++ b/src/main/java/li/cil/oc2/api/bus/package-info.java @@ -1,3 +1,5 @@ +/* SPDX-License-Identifier: MIT */ + /** * The device bus is the glue that connects devices and VMs. *

@@ -14,15 +16,15 @@ * {@link li.cil.oc2.api.bus.DeviceBusElement}s are responsible for * providing a list of devices connected to them. Whether they play an * active role and seek out devices, or passively expect devices to be - * registered with them depends on the implementation implementation. + * registered with them depends on the implementation. *

* After a scan {@link li.cil.oc2.api.bus.DeviceBusController}s then * collect all devices from all bus elements to build a global set * of devices on the bus. *

* There can be various types of devices on a bus, but which types are - * supported will depend on the context of the controller. Currently two - * types of devices are are defined in this API, {@link li.cil.oc2.api.bus.device.rpc.RPCDevice} + * supported will depend on the context of the controller. Currently, two + * types of devices are defined in this API, {@link li.cil.oc2.api.bus.device.rpc.RPCDevice} * and {@link li.cil.oc2.api.bus.device.vm.VMDevice}. *