diff options
Diffstat (limited to 'modules')
41 files changed, 3551 insertions, 40 deletions
diff --git a/modules/bullet/bullet_physics_server.cpp b/modules/bullet/bullet_physics_server.cpp index 51de4998fa..b646fc164d 100644 --- a/modules/bullet/bullet_physics_server.cpp +++ b/modules/bullet/bullet_physics_server.cpp @@ -70,8 +70,8 @@ return RID(); \ } -#define AddJointToSpace(body, joint, disableCollisionsBetweenLinkedBodies) \ - body->get_space()->add_constraint(joint, disableCollisionsBetweenLinkedBodies); +#define AddJointToSpace(body, joint) \ + body->get_space()->add_constraint(joint, joint->is_disabled_collisions_between_bodies()); // <--------------- Joint creation asserts btEmptyShape *BulletPhysicsServer::emptyShape(ShapeBullet::create_shape_empty()); @@ -987,6 +987,20 @@ int BulletPhysicsServer::joint_get_solver_priority(RID p_joint) const { return 0; } +void BulletPhysicsServer::joint_disable_collisions_between_bodies(RID p_joint, const bool p_disable) { + JointBullet *joint = joint_owner.get(p_joint); + ERR_FAIL_COND(!joint); + + joint->disable_collisions_between_bodies(p_disable); +} + +bool BulletPhysicsServer::joint_is_disabled_collisions_between_bodies(RID p_joint) const { + JointBullet *joint(joint_owner.get(p_joint)); + ERR_FAIL_COND_V(!joint, false); + + return joint->is_disabled_collisions_between_bodies(); +} + RID BulletPhysicsServer::joint_create_pin(RID p_body_A, const Vector3 &p_local_A, RID p_body_B, const Vector3 &p_local_B) { RigidBodyBullet *body_A = rigid_body_owner.get(p_body_A); ERR_FAIL_COND_V(!body_A, RID()); @@ -1003,7 +1017,7 @@ RID BulletPhysicsServer::joint_create_pin(RID p_body_A, const Vector3 &p_local_A ERR_FAIL_COND_V(body_A == body_B, RID()); JointBullet *joint = bulletnew(PinJointBullet(body_A, p_local_A, body_B, p_local_B)); - AddJointToSpace(body_A, joint, true); + AddJointToSpace(body_A, joint); CreateThenReturnRID(joint_owner, joint); } @@ -1071,7 +1085,7 @@ RID BulletPhysicsServer::joint_create_hinge(RID p_body_A, const Transform &p_hin ERR_FAIL_COND_V(body_A == body_B, RID()); JointBullet *joint = bulletnew(HingeJointBullet(body_A, body_B, p_hinge_A, p_hinge_B)); - AddJointToSpace(body_A, joint, true); + AddJointToSpace(body_A, joint); CreateThenReturnRID(joint_owner, joint); } @@ -1091,7 +1105,7 @@ RID BulletPhysicsServer::joint_create_hinge_simple(RID p_body_A, const Vector3 & ERR_FAIL_COND_V(body_A == body_B, RID()); JointBullet *joint = bulletnew(HingeJointBullet(body_A, body_B, p_pivot_A, p_pivot_B, p_axis_A, p_axis_B)); - AddJointToSpace(body_A, joint, true); + AddJointToSpace(body_A, joint); CreateThenReturnRID(joint_owner, joint); } @@ -1143,7 +1157,7 @@ RID BulletPhysicsServer::joint_create_slider(RID p_body_A, const Transform &p_lo ERR_FAIL_COND_V(body_A == body_B, RID()); JointBullet *joint = bulletnew(SliderJointBullet(body_A, body_B, p_local_frame_A, p_local_frame_B)); - AddJointToSpace(body_A, joint, true); + AddJointToSpace(body_A, joint); CreateThenReturnRID(joint_owner, joint); } @@ -1177,7 +1191,7 @@ RID BulletPhysicsServer::joint_create_cone_twist(RID p_body_A, const Transform & } JointBullet *joint = bulletnew(ConeTwistJointBullet(body_A, body_B, p_local_frame_A, p_local_frame_B)); - AddJointToSpace(body_A, joint, true); + AddJointToSpace(body_A, joint); CreateThenReturnRID(joint_owner, joint); } @@ -1213,7 +1227,7 @@ RID BulletPhysicsServer::joint_create_generic_6dof(RID p_body_A, const Transform ERR_FAIL_COND_V(body_A == body_B, RID()); JointBullet *joint = bulletnew(Generic6DOFJointBullet(body_A, body_B, p_local_frame_A, p_local_frame_B, true)); - AddJointToSpace(body_A, joint, true); + AddJointToSpace(body_A, joint); CreateThenReturnRID(joint_owner, joint); } diff --git a/modules/bullet/bullet_physics_server.h b/modules/bullet/bullet_physics_server.h index e0e46cd369..764ec2387c 100644 --- a/modules/bullet/bullet_physics_server.h +++ b/modules/bullet/bullet_physics_server.h @@ -290,6 +290,9 @@ public: virtual void joint_set_solver_priority(RID p_joint, int p_priority); virtual int joint_get_solver_priority(RID p_joint) const; + virtual void joint_disable_collisions_between_bodies(RID p_joint, const bool p_disable); + virtual bool joint_is_disabled_collisions_between_bodies(RID p_joint) const; + virtual RID joint_create_pin(RID p_body_A, const Vector3 &p_local_A, RID p_body_B, const Vector3 &p_local_B); virtual void pin_joint_set_param(RID p_joint, PinJointParam p_param, float p_value); diff --git a/modules/bullet/constraint_bullet.cpp b/modules/bullet/constraint_bullet.cpp index b60e89b6fd..d15fb8de01 100644 --- a/modules/bullet/constraint_bullet.cpp +++ b/modules/bullet/constraint_bullet.cpp @@ -39,7 +39,8 @@ ConstraintBullet::ConstraintBullet() : space(NULL), - constraint(NULL) {} + constraint(NULL), + disabled_collisions_between_bodies(true) {} void ConstraintBullet::setup(btTypedConstraint *p_constraint) { constraint = p_constraint; @@ -53,3 +54,12 @@ void ConstraintBullet::set_space(SpaceBullet *p_space) { void ConstraintBullet::destroy_internal_constraint() { space->remove_constraint(this); } + +void ConstraintBullet::disable_collisions_between_bodies(const bool p_disabled) { + disabled_collisions_between_bodies = p_disabled; + + if (space) { + space->remove_constraint(this); + space->add_constraint(this, disabled_collisions_between_bodies); + } +} diff --git a/modules/bullet/constraint_bullet.h b/modules/bullet/constraint_bullet.h index 23be5a5063..ed3a318cbc 100644 --- a/modules/bullet/constraint_bullet.h +++ b/modules/bullet/constraint_bullet.h @@ -49,6 +49,7 @@ class ConstraintBullet : public RIDBullet { protected: SpaceBullet *space; btTypedConstraint *constraint; + bool disabled_collisions_between_bodies; public: ConstraintBullet(); @@ -57,6 +58,9 @@ public: virtual void set_space(SpaceBullet *p_space); virtual void destroy_internal_constraint(); + void disable_collisions_between_bodies(const bool p_disabled); + _FORCE_INLINE_ bool is_disabled_collisions_between_bodies() const { return disabled_collisions_between_bodies; } + public: virtual ~ConstraintBullet() { bulletdelete(constraint); diff --git a/modules/gdnative/SCsub b/modules/gdnative/SCsub index c92c3f30a2..acfb83bc10 100644 --- a/modules/gdnative/SCsub +++ b/modules/gdnative/SCsub @@ -28,7 +28,7 @@ def _build_gdnative_api_struct_header(api): '\textern const godot_gdnative_ext_{0}_api_struct *_gdnative_wrapper_{0}_api_struct;'.format(name)) gdnative_api_init_macro.append('\t_gdnative_wrapper_api_struct = options->api_struct;') - gdnative_api_init_macro.append('\tfor (int i = 0; i < _gdnative_wrapper_api_struct->num_extensions; i++) { ') + gdnative_api_init_macro.append('\tfor (unsigned int i = 0; i < _gdnative_wrapper_api_struct->num_extensions; i++) { ') gdnative_api_init_macro.append('\t\tswitch (_gdnative_wrapper_api_struct->extensions[i]->type) {') for name in api['extensions']: @@ -66,19 +66,30 @@ def _build_gdnative_api_struct_header(api): out += ['};', ''] - for name in api['extensions']: - out += [ - 'typedef struct godot_gdnative_ext_' + name + '_api_struct {', + + def generate_extension_struct(name, ext, include_version=True): + ret_val = [] + if ext['next']: + ret_val += generate_extension_struct(name, ext['next']) + + ret_val += [ + 'typedef struct godot_gdnative_ext_' + name + ('' if not include_version else ('_{0}_{1}'.format(ext['version']['major'], ext['version']['minor']))) + '_api_struct {', '\tunsigned int type;', '\tgodot_gdnative_api_version version;', '\tconst godot_gdnative_api_struct *next;' ] - for funcdef in api['extensions'][name]['api']: + for funcdef in ext['api']: args = ', '.join(['%s%s' % (_spaced(t), n) for t, n in funcdef['arguments']]) - out.append('\t%s(*%s)(%s);' % (_spaced(funcdef['return_type']), funcdef['name'], args)) + ret_val.append('\t%s(*%s)(%s);' % (_spaced(funcdef['return_type']), funcdef['name'], args)) + + ret_val += ['} godot_gdnative_ext_' + name + ('' if not include_version else ('_{0}_{1}'.format(ext['version']['major'], ext['version']['minor']))) + '_api_struct;', ''] + + return ret_val - out += ['} godot_gdnative_ext_' + name + '_api_struct;', ''] + + for name in api['extensions']: + out += generate_extension_struct(name, api['extensions'][name], False) out += [ 'typedef struct godot_gdnative_core_api_struct {', @@ -113,18 +124,35 @@ def _build_gdnative_api_struct_source(api): '' ] - for name in api['extensions']: - out += [ - 'extern const godot_gdnative_ext_' + name + '_api_struct api_extension_' + name + '_struct = {', - '\tGDNATIVE_EXT_' + api['extensions'][name]['type'] + ',', - '\t{' + str(api['extensions'][name]['version']['major']) + ', ' + str(api['extensions'][name]['version']['minor']) + '},', - '\tNULL,' + def get_extension_struct_name(name, ext, include_version=True): + return 'godot_gdnative_ext_' + name + ('' if not include_version else ('_{0}_{1}'.format(ext['version']['major'], ext['version']['minor']))) + '_api_struct' + + def get_extension_struct_instance_name(name, ext, include_version=True): + return 'api_extension_' + name + ('' if not include_version else ('_{0}_{1}'.format(ext['version']['major'], ext['version']['minor']))) + '_struct' + + def get_extension_struct_definition(name, ext, include_version=True): + + ret_val = [] + + if ext['next']: + ret_val += get_extension_struct_definition(name, ext['next']) + + ret_val += [ + 'extern const ' + get_extension_struct_name(name, ext, include_version) + ' ' + get_extension_struct_instance_name(name, ext, include_version) + ' = {', + '\tGDNATIVE_EXT_' + ext['type'] + ',', + '\t{' + str(ext['version']['major']) + ', ' + str(ext['version']['minor']) + '},', + '\t' + ('NULL' if not ext['next'] else ('(const godot_gdnative_api_struct *)&' + get_extension_struct_instance_name(name, ext['next']))) + ',' ] - for funcdef in api['extensions'][name]['api']: - out.append('\t%s,' % funcdef['name']) + for funcdef in ext['api']: + ret_val.append('\t%s,' % funcdef['name']) + + ret_val += ['};\n'] - out += ['};\n'] + return ret_val + + for name in api['extensions']: + out += get_extension_struct_definition(name, api['extensions'][name], False) out += ['', 'const godot_gdnative_api_struct *gdnative_extensions_pointers[] = {'] diff --git a/modules/gdnative/doc_classes/NativeScript.xml b/modules/gdnative/doc_classes/NativeScript.xml index f713e4112e..6a71cd8d4d 100644 --- a/modules/gdnative/doc_classes/NativeScript.xml +++ b/modules/gdnative/doc_classes/NativeScript.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="NativeScript" inherits="Script" category="Core" version="3.0-stable"> +<class name="NativeScript" inherits="Script" category="Core" version="3.1-dev"> <brief_description> </brief_description> <description> @@ -9,10 +9,46 @@ <demos> </demos> <methods> + <method name="get_class_documentation" qualifiers="const"> + <return type="String"> + </return> + <description> + Returns the documentation string that was previously set with [code]godot_nativescript_set_class_documentation[/code]. + </description> + </method> + <method name="get_method_documentation" qualifiers="const"> + <return type="String"> + </return> + <argument index="0" name="method" type="String"> + </argument> + <description> + Returns the documentation string that was previously set with [code]godot_nativescript_set_method_documentation[/code]. + </description> + </method> + <method name="get_property_documentation" qualifiers="const"> + <return type="String"> + </return> + <argument index="0" name="path" type="String"> + </argument> + <description> + Returns the documentation string that was previously set with [code]godot_nativescript_set_property_documentation[/code]. + </description> + </method> + <method name="get_signal_documentation" qualifiers="const"> + <return type="String"> + </return> + <argument index="0" name="signal_name" type="String"> + </argument> + <description> + Returns the documentation string that was previously set with [code]godot_nativescript_set_signal_documentation[/code]. + </description> + </method> <method name="new" qualifiers="vararg"> <return type="Object"> </return> <description> + Constructs a new object of the base type with a script of this type already attached. + [i]Note[/i]: Any arguments passed to this function will be ignored and not passed to the native constructor function. This will change with in a future API extension. </description> </method> </methods> diff --git a/modules/gdnative/gdnative_api.json b/modules/gdnative/gdnative_api.json index 59a9c0b090..a8919f7130 100644 --- a/modules/gdnative/gdnative_api.json +++ b/modules/gdnative/gdnative_api.json @@ -5,6 +5,7 @@ "major": 1, "minor": 0 }, + "next": null, "api": [ { "name": "godot_color_new_rgba", @@ -3963,7 +3964,7 @@ "name": "godot_variant_new_bool", "return_type": "void", "arguments": [ - ["godot_variant *", "p_v"], + ["godot_variant *", "r_dest"], ["const godot_bool", "p_b"] ] }, @@ -5762,6 +5763,104 @@ "major": 1, "minor": 0 }, + "next": { + "type": "NATIVESCRIPT", + "version": { + "major": 1, + "minor": 1 + }, + "next": null, + "api": [ + { + "name": "godot_nativescript_set_method_argument_information", + "return_type": "void", + "arguments": [ + ["void *", "p_gdnative_handle"], + ["const char *", "p_name"], + ["const char *", "p_function_name"], + ["int", "p_num_args"], + ["const godot_method_arg *", "p_args"] + ] + }, + { + "name": "godot_nativescript_set_class_documentation", + "return_type": "void", + "arguments": [ + ["void *", "p_gdnative_handle"], + ["const char *", "p_name"], + ["godot_string", "p_documentation"] + ] + }, + { + "name": "godot_nativescript_set_method_documentation", + "return_type": "void", + "arguments": [ + ["void *", "p_gdnative_handle"], + ["const char *", "p_name"], + ["const char *", "p_function_name"], + ["godot_string", "p_documentation"] + ] + }, + { + "name": "godot_nativescript_set_property_documentation", + "return_type": "void", + "arguments": [ + ["void *", "p_gdnative_handle"], + ["const char *", "p_name"], + ["const char *", "p_path"], + ["godot_string", "p_documentation"] + ] + }, + { + "name": "godot_nativescript_set_signal_documentation", + "return_type": "void", + "arguments": [ + ["void *", "p_gdnative_handle"], + ["const char *", "p_name"], + ["const char *", "p_signal_name"], + ["godot_string", "p_documentation"] + ] + }, + { + "name": "godot_nativescript_set_type_tag", + "return_type": "void", + "arguments": [ + ["void *", "p_gdnative_handle"], + ["const char *", "p_name"], + ["const void *", "p_type_tag"] + ] + }, + { + "name": "godot_nativescript_get_type_tag", + "return_type": "const void *", + "arguments": [ + ["const godot_object *", "p_object"] + ] + }, + { + "name": "godot_nativescript_register_instance_binding_data_functions", + "return_type": "int", + "arguments": [ + ["godot_instance_binding_functions", "p_binding_functions"] + ] + }, + { + "name": "godot_nativescript_unregister_instance_binding_data_functions", + "return_type": "void", + "arguments": [ + ["int", "p_idx"] + ] + }, + { + "name": "godot_nativescript_get_instance_binding_data", + "return_type": "void *", + "arguments": [ + ["int", "p_idx"], + ["godot_object *", "p_object"] + ] + } + ] + }, "api": [ { "name": "godot_nativescript_register_class", @@ -5832,6 +5931,7 @@ "major": 1, "minor": 0 }, + "next": null, "api": [ { "name": "godot_pluginscript_register_language", @@ -5848,6 +5948,7 @@ "major": 1, "minor": 0 }, + "next": null, "api": [ { "name": "godot_arvr_register_interface", diff --git a/modules/gdnative/include/gdnative/variant.h b/modules/gdnative/include/gdnative/variant.h index d2e8246bfb..6779dc4092 100644 --- a/modules/gdnative/include/gdnative/variant.h +++ b/modules/gdnative/include/gdnative/variant.h @@ -135,7 +135,7 @@ void GDAPI godot_variant_new_copy(godot_variant *r_dest, const godot_variant *p_ void GDAPI godot_variant_new_nil(godot_variant *r_dest); -void GDAPI godot_variant_new_bool(godot_variant *p_v, const godot_bool p_b); +void GDAPI godot_variant_new_bool(godot_variant *r_dest, const godot_bool p_b); void GDAPI godot_variant_new_uint(godot_variant *r_dest, const uint64_t p_i); void GDAPI godot_variant_new_int(godot_variant *r_dest, const int64_t p_i); void GDAPI godot_variant_new_real(godot_variant *r_dest, const double p_r); diff --git a/modules/gdnative/include/nativescript/godot_nativescript.h b/modules/gdnative/include/nativescript/godot_nativescript.h index 11017ae78d..747328bc41 100644 --- a/modules/gdnative/include/nativescript/godot_nativescript.h +++ b/modules/gdnative/include/nativescript/godot_nativescript.h @@ -185,6 +185,52 @@ void GDAPI godot_nativescript_register_signal(void *p_gdnative_handle, const cha void GDAPI *godot_nativescript_get_userdata(godot_object *p_instance); +/* + * + * + * NativeScript 1.1 + * + * + */ + +// method registering with argument names + +typedef struct { + godot_string name; + + godot_variant_type type; + godot_property_hint hint; + godot_string hint_string; +} godot_method_arg; + +void GDAPI godot_nativescript_set_method_argument_information(void *p_gdnative_handle, const char *p_name, const char *p_function_name, int p_num_args, const godot_method_arg *p_args); + +// documentation + +void GDAPI godot_nativescript_set_class_documentation(void *p_gdnative_handle, const char *p_name, godot_string p_documentation); +void GDAPI godot_nativescript_set_method_documentation(void *p_gdnative_handle, const char *p_name, const char *p_function_name, godot_string p_documentation); +void GDAPI godot_nativescript_set_property_documentation(void *p_gdnative_handle, const char *p_name, const char *p_path, godot_string p_documentation); +void GDAPI godot_nativescript_set_signal_documentation(void *p_gdnative_handle, const char *p_name, const char *p_signal_name, godot_string p_documentation); + +// type tag API + +void GDAPI godot_nativescript_set_type_tag(void *p_gdnative_handle, const char *p_name, const void *p_type_tag); +const void GDAPI *godot_nativescript_get_type_tag(const godot_object *p_object); + +// instance binding API + +typedef struct { + void *(*alloc_instance_binding_data)(void *, godot_object *); + void (*free_instance_binding_data)(void *, void *); + void *data; + void (*free_func)(void *); +} godot_instance_binding_functions; + +int GDAPI godot_nativescript_register_instance_binding_data_functions(godot_instance_binding_functions p_binding_functions); +void GDAPI godot_nativescript_unregister_instance_binding_data_functions(int p_idx); + +void GDAPI *godot_nativescript_get_instance_binding_data(int p_idx, godot_object *p_object); + #ifdef __cplusplus } #endif diff --git a/modules/gdnative/nativescript/godot_nativescript.cpp b/modules/gdnative/nativescript/godot_nativescript.cpp index b4f7e1555e..aea595d0f0 100644 --- a/modules/gdnative/nativescript/godot_nativescript.cpp +++ b/modules/gdnative/nativescript/godot_nativescript.cpp @@ -106,7 +106,7 @@ void GDAPI godot_nativescript_register_method(void *p_gdnative_handle, const cha Map<StringName, NativeScriptDesc>::Element *E = NSL->library_classes[*s].find(p_name); if (!E) { - ERR_EXPLAIN("Attempt to register method on non-existant class!"); + ERR_EXPLAIN("Attempted to register method on non-existent class!"); ERR_FAIL(); } @@ -125,7 +125,7 @@ void GDAPI godot_nativescript_register_property(void *p_gdnative_handle, const c Map<StringName, NativeScriptDesc>::Element *E = NSL->library_classes[*s].find(p_name); if (!E) { - ERR_EXPLAIN("Attempt to register method on non-existant class!"); + ERR_EXPLAIN("Attempted to register method on non-existent class!"); ERR_FAIL(); } @@ -150,7 +150,7 @@ void GDAPI godot_nativescript_register_signal(void *p_gdnative_handle, const cha Map<StringName, NativeScriptDesc>::Element *E = NSL->library_classes[*s].find(p_name); if (!E) { - ERR_EXPLAIN("Attempt to register method on non-existant class!"); + ERR_EXPLAIN("Attempted to register method on non-existent class!"); ERR_FAIL(); } @@ -201,6 +201,164 @@ void GDAPI *godot_nativescript_get_userdata(godot_object *p_instance) { return NULL; } +/* + * + * + * NativeScript 1.1 + * + * + */ + +void GDAPI godot_nativescript_set_method_argument_information(void *p_gdnative_handle, const char *p_name, const char *p_function_name, int p_num_args, const godot_method_arg *p_args) { + String *s = (String *)p_gdnative_handle; + + Map<StringName, NativeScriptDesc>::Element *E = NSL->library_classes[*s].find(p_name); + + if (!E) { + ERR_EXPLAIN("Attempted to add argument information for a method on a non-existent class!"); + ERR_FAIL(); + } + + Map<StringName, NativeScriptDesc::Method>::Element *method = E->get().methods.find(p_function_name); + if (!method) { + ERR_EXPLAIN("Attempted to add argument information to non-existent method!"); + ERR_FAIL(); + } + + MethodInfo *method_information = &method->get().info; + + List<PropertyInfo> args; + + for (int i = 0; i < p_num_args; i++) { + godot_method_arg arg = p_args[i]; + String name = *(String *)&arg.name; + String hint_string = *(String *)&arg.hint_string; + + Variant::Type type = (Variant::Type)arg.type; + PropertyHint hint = (PropertyHint)arg.hint; + + args.push_back(PropertyInfo(type, p_name, hint, hint_string)); + } + + method_information->arguments = args; +} + +void GDAPI godot_nativescript_set_class_documentation(void *p_gdnative_handle, const char *p_name, godot_string p_documentation) { + String *s = (String *)p_gdnative_handle; + + Map<StringName, NativeScriptDesc>::Element *E = NSL->library_classes[*s].find(p_name); + + if (!E) { + ERR_EXPLAIN("Attempted to add documentation to a non-existent class!"); + ERR_FAIL(); + } + + E->get().documentation = *(String *)&p_documentation; +} + +void GDAPI godot_nativescript_set_method_documentation(void *p_gdnative_handle, const char *p_name, const char *p_function_name, godot_string p_documentation) { + String *s = (String *)p_gdnative_handle; + + Map<StringName, NativeScriptDesc>::Element *E = NSL->library_classes[*s].find(p_name); + + if (!E) { + ERR_EXPLAIN("Attempted to add documentation to a method on a non-existent class!"); + ERR_FAIL(); + } + + Map<StringName, NativeScriptDesc::Method>::Element *method = E->get().methods.find(p_function_name); + if (!method) { + ERR_EXPLAIN("Attempted to add documentatino to non-existent method!"); + ERR_FAIL(); + } + + method->get().documentation = *(String *)&p_documentation; +} + +void GDAPI godot_nativescript_set_property_documentation(void *p_gdnative_handle, const char *p_name, const char *p_path, godot_string p_documentation) { + String *s = (String *)p_gdnative_handle; + + Map<StringName, NativeScriptDesc>::Element *E = NSL->library_classes[*s].find(p_name); + + if (!E) { + ERR_EXPLAIN("Attempted to add documentation to a property on a non-existent class!"); + ERR_FAIL(); + } + + OrderedHashMap<StringName, NativeScriptDesc::Property>::Element property = E->get().properties.find(p_path); + if (!property) { + ERR_EXPLAIN("Attempted to add documentation to non-existent property!"); + ERR_FAIL(); + } + + property.get().documentation = *(String *)&p_documentation; +} + +void GDAPI godot_nativescript_set_signal_documentation(void *p_gdnative_handle, const char *p_name, const char *p_signal_name, godot_string p_documentation) { + String *s = (String *)p_gdnative_handle; + + Map<StringName, NativeScriptDesc>::Element *E = NSL->library_classes[*s].find(p_name); + + if (!E) { + ERR_EXPLAIN("Attempted to add documentation to a signal on a non-existent class!"); + ERR_FAIL(); + } + + Map<StringName, NativeScriptDesc::Signal>::Element *signal = E->get().signals_.find(p_signal_name); + if (!signal) { + ERR_EXPLAIN("Attempted to add documentation to non-existent signal!"); + ERR_FAIL(); + } + + signal->get().documentation = *(String *)&p_documentation; +} + +void GDAPI godot_nativescript_set_type_tag(void *p_gdnative_handle, const char *p_name, const void *p_type_tag) { + String *s = (String *)p_gdnative_handle; + + Map<StringName, NativeScriptDesc>::Element *E = NSL->library_classes[*s].find(p_name); + + if (!E) { + ERR_EXPLAIN("Attempted to set type tag on a non-existent class!"); + ERR_FAIL(); + } + + E->get().type_tag = p_type_tag; +} + +const void GDAPI *godot_nativescript_get_type_tag(const godot_object *p_object) { + + const Object *o = (Object *)p_object; + + if (!o->get_script_instance()) { + ERR_EXPLAIN("Attempted to get type tag on an object without a script!"); + ERR_FAIL_V(NULL); + } else { + NativeScript *script = Object::cast_to<NativeScript>(o->get_script_instance()->get_script().ptr()); + if (!script) { + ERR_EXPLAIN("Attempted to get type tag on an object without a nativescript attached"); + ERR_FAIL_V(NULL); + } + + if (script->get_script_desc()) + return script->get_script_desc()->type_tag; + } + + return NULL; +} + #ifdef __cplusplus } #endif + +int GDAPI godot_nativescript_register_instance_binding_data_functions(godot_instance_binding_functions p_binding_functions) { + return NativeScriptLanguage::get_singleton()->register_binding_functions(p_binding_functions); +} + +void GDAPI godot_nativescript_unregister_instance_binding_data_functions(int p_idx) { + NativeScriptLanguage::get_singleton()->unregister_binding_functions(p_idx); +} + +void GDAPI *godot_nativescript_get_instance_binding_data(int p_idx, godot_object *p_object) { + return NativeScriptLanguage::get_singleton()->get_instance_binding_data(p_idx, (Object *)p_object); +} diff --git a/modules/gdnative/nativescript/nativescript.cpp b/modules/gdnative/nativescript/nativescript.cpp index aaa7d634d1..f2e9bef467 100644 --- a/modules/gdnative/nativescript/nativescript.cpp +++ b/modules/gdnative/nativescript/nativescript.cpp @@ -68,6 +68,11 @@ void NativeScript::_bind_methods() { ClassDB::bind_method(D_METHOD("set_library", "library"), &NativeScript::set_library); ClassDB::bind_method(D_METHOD("get_library"), &NativeScript::get_library); + ClassDB::bind_method(D_METHOD("get_class_documentation"), &NativeScript::get_class_documentation); + ClassDB::bind_method(D_METHOD("get_method_documentation", "method"), &NativeScript::get_method_documentation); + ClassDB::bind_method(D_METHOD("get_signal_documentation", "signal_name"), &NativeScript::get_signal_documentation); + ClassDB::bind_method(D_METHOD("get_property_documentation", "path"), &NativeScript::get_property_documentation); + ADD_PROPERTYNZ(PropertyInfo(Variant::STRING, "class_name"), "set_class_name", "get_class_name"); ADD_PROPERTYNZ(PropertyInfo(Variant::OBJECT, "library", PROPERTY_HINT_RESOURCE_TYPE, "GDNativeLibrary"), "set_library", "get_library"); @@ -373,6 +378,86 @@ void NativeScript::get_script_property_list(List<PropertyInfo> *p_list) const { } } +String NativeScript::get_class_documentation() const { + NativeScriptDesc *script_data = get_script_desc(); + + if (!script_data) { + ERR_EXPLAIN("Attempt to get class documentation on invalid NativeScript"); + ERR_FAIL_V(""); + } + + return script_data->documentation; +} + +String NativeScript::get_method_documentation(const StringName &p_method) const { + NativeScriptDesc *script_data = get_script_desc(); + + if (!script_data) { + ERR_EXPLAIN("Attempt to get method documentation on invalid NativeScript"); + ERR_FAIL_V(""); + } + + while (script_data) { + + Map<StringName, NativeScriptDesc::Method>::Element *method = script_data->methods.find(p_method); + + if (method) { + return method->get().documentation; + } + + script_data = script_data->base_data; + } + + ERR_EXPLAIN("Attempt to get method documentation for non-existent method"); + ERR_FAIL_V(""); +} + +String NativeScript::get_signal_documentation(const StringName &p_signal_name) const { + NativeScriptDesc *script_data = get_script_desc(); + + if (!script_data) { + ERR_EXPLAIN("Attempt to get signal documentation on invalid NativeScript"); + ERR_FAIL_V(""); + } + + while (script_data) { + + Map<StringName, NativeScriptDesc::Signal>::Element *signal = script_data->signals_.find(p_signal_name); + + if (signal) { + return signal->get().documentation; + } + + script_data = script_data->base_data; + } + + ERR_EXPLAIN("Attempt to get signal documentation for non-existent signal"); + ERR_FAIL_V(""); +} + +String NativeScript::get_property_documentation(const StringName &p_path) const { + NativeScriptDesc *script_data = get_script_desc(); + + if (!script_data) { + ERR_EXPLAIN("Attempt to get property documentation on invalid NativeScript"); + ERR_FAIL_V(""); + } + + while (script_data) { + + OrderedHashMap<StringName, NativeScriptDesc::Property>::Element property = script_data->properties.find(p_path); + + if (property) { + return property.get().documentation; + } + + script_data = script_data->base_data; + } + + ERR_EXPLAIN("Attempt to get property documentation for non-existent signal"); + ERR_FAIL_V(""); +} + Variant NativeScript::_new(const Variant **p_args, int p_argcount, Variant::CallError &r_error) { if (lib_path.empty() || class_name.empty() || library.is_null()) { @@ -610,7 +695,7 @@ Variant::Type NativeScriptInstance::get_property_type(const StringName &p_name, } void NativeScriptInstance::get_method_list(List<MethodInfo> *p_list) const { - script->get_method_list(p_list); + script->get_script_method_list(p_list); } bool NativeScriptInstance::has_method(const StringName &p_method) const { @@ -824,6 +909,25 @@ void NativeScriptLanguage::_unload_stuff(bool p_reload) { } } + Map<String, Ref<GDNative> >::Element *E = library_gdnatives.find(lib_path); + Ref<GDNative> gdn; + + if (E) { + gdn = E->get(); + } + + if (gdn.is_valid() && gdn->get_library().is_valid()) { + Ref<GDNativeLibrary> lib = gdn->get_library(); + void *terminate_fn; + Error err = gdn->get_symbol(lib->get_symbol_prefix() + _terminate_call_name, terminate_fn, true); + + if (err == OK) { + void (*terminate)(void *) = (void (*)(void *))terminate_fn; + + terminate((void *)&lib_path); + } + } + for (Map<StringName, NativeScriptDesc>::Element *C = classes.front(); C; C = C->next()) { // free property stuff first @@ -1011,6 +1115,116 @@ int NativeScriptLanguage::profiling_get_frame_data(ProfilingInfo *p_info_arr, in return 0; } +int NativeScriptLanguage::register_binding_functions(godot_instance_binding_functions p_binding_functions) { + + // find index + + int idx = -1; + + for (int i = 0; i < binding_functions.size(); i++) { + if (!binding_functions[i].first) { + // free, we'll take it + idx = i; + break; + } + } + + if (idx == -1) { + idx = binding_functions.size(); + binding_functions.resize(idx + 1); + } + + // set the functions + binding_functions[idx].first = true; + binding_functions[idx].second = p_binding_functions; + + return idx; +} + +void NativeScriptLanguage::unregister_binding_functions(int p_idx) { + ERR_FAIL_INDEX(p_idx, binding_functions.size()); + + for (Set<Vector<void *> *>::Element *E = binding_instances.front(); E; E = E->next()) { + Vector<void *> &binding_data = *E->get(); + + if (binding_data[p_idx] && binding_functions[p_idx].second.free_instance_binding_data) + binding_functions[p_idx].second.free_instance_binding_data(binding_functions[p_idx].second.data, binding_data[p_idx]); + } + + binding_functions[p_idx].first = false; + + if (binding_functions[p_idx].second.free_func) + binding_functions[p_idx].second.free_func(binding_functions[p_idx].second.data); +} + +void *NativeScriptLanguage::get_instance_binding_data(int p_idx, Object *p_object) { + ERR_FAIL_INDEX_V(p_idx, binding_functions.size(), NULL); + + if (!binding_functions[p_idx].first) { + ERR_EXPLAIN("Tried to get binding data for a nativescript binding that does not exist"); + ERR_FAIL_V(NULL); + } + + Vector<void *> *binding_data = (Vector<void *> *)p_object->get_script_instance_binding(lang_idx); + + if (!binding_data) + return NULL; // should never happen. + + if (binding_data->size() <= p_idx) { + // okay, add new elements here. + int old_size = binding_data->size(); + + binding_data->resize(p_idx + 1); + + for (int i = old_size; i <= p_idx; i++) { + (*binding_data)[i] = NULL; + } + } + + if (!(*binding_data)[p_idx]) { + // no binding data yet, soooooo alloc new one \o/ + (*binding_data)[p_idx] = binding_functions[p_idx].second.alloc_instance_binding_data(binding_functions[p_idx].second.data, (godot_object *)p_object); + } + + return (*binding_data)[p_idx]; +} + +void *NativeScriptLanguage::alloc_instance_binding_data(Object *p_object) { + + Vector<void *> *binding_data = new Vector<void *>; + + binding_data->resize(binding_functions.size()); + + for (int i = 0; i < binding_functions.size(); i++) { + (*binding_data)[i] = NULL; + } + + binding_instances.insert(binding_data); + + return (void *)binding_data; +} + +void NativeScriptLanguage::free_instance_binding_data(void *p_data) { + + if (!p_data) + return; + + Vector<void *> &binding_data = *(Vector<void *> *)p_data; + + for (int i = 0; i < binding_data.size(); i++) { + if (!binding_data[i]) + continue; + + if (binding_functions[i].first && binding_functions[i].second.free_instance_binding_data) { + binding_functions[i].second.free_instance_binding_data(binding_functions[i].second.data, binding_data[i]); + } + } + + binding_instances.erase(&binding_data); + + delete &binding_data; +} + #ifndef NO_THREADS void NativeScriptLanguage::defer_init_library(Ref<GDNativeLibrary> lib, NativeScript *script) { MutexLock lock(mutex); diff --git a/modules/gdnative/nativescript/nativescript.h b/modules/gdnative/nativescript/nativescript.h index ac94c84bc4..17b6ddc747 100644 --- a/modules/gdnative/nativescript/nativescript.h +++ b/modules/gdnative/nativescript/nativescript.h @@ -53,6 +53,7 @@ struct NativeScriptDesc { godot_instance_method method; MethodInfo info; int rpc_mode; + String documentation; }; struct Property { godot_property_set_func setter; @@ -60,12 +61,16 @@ struct NativeScriptDesc { PropertyInfo info; Variant default_value; int rset_mode; + String documentation; }; struct Signal { MethodInfo signal; + String documentation; }; + String documentation; + Map<StringName, Method> methods; OrderedHashMap<StringName, Property> properties; Map<StringName, Signal> signals_; // QtCreator doesn't like the name signals @@ -75,6 +80,8 @@ struct NativeScriptDesc { godot_instance_create_func create_func; godot_instance_destroy_func destroy_func; + const void *type_tag; + bool is_tool; inline NativeScriptDesc() : @@ -82,7 +89,9 @@ struct NativeScriptDesc { properties(), signals_(), base(), - base_native_type() { + base_native_type(), + documentation(), + type_tag(NULL) { zeromem(&create_func, sizeof(godot_instance_create_func)); zeromem(&destroy_func, sizeof(godot_instance_destroy_func)); } @@ -154,6 +163,11 @@ public: virtual void get_script_method_list(List<MethodInfo> *p_list) const; virtual void get_script_property_list(List<PropertyInfo> *p_list) const; + String get_class_documentation() const; + String get_method_documentation(const StringName &p_method) const; + String get_signal_documentation(const StringName &p_signal_name) const; + String get_property_documentation(const StringName &p_path) const; + Variant _new(const Variant **p_args, int p_argcount, Variant::CallError &r_error); NativeScript(); @@ -204,6 +218,7 @@ class NativeScriptLanguage : public ScriptLanguage { private: static NativeScriptLanguage *singleton; + int lang_idx; void _unload_stuff(bool p_reload = false); @@ -222,6 +237,9 @@ private: void call_libraries_cb(const StringName &name); + Vector<Pair<bool, godot_instance_binding_functions> > binding_functions; + Set<Vector<void *> *> binding_instances; + public: // These two maps must only be touched on the main thread Map<String, Map<StringName, NativeScriptDesc> > library_classes; @@ -232,6 +250,8 @@ public: const StringName _init_call_type = "nativescript_init"; const StringName _init_call_name = "nativescript_init"; + const StringName _terminate_call_name = "nativescript_terminate"; + const StringName _noarg_call_type = "nativescript_no_arg"; const StringName _frame_call_name = "nativescript_frame"; @@ -250,6 +270,8 @@ public: void _hacky_api_anchor(); + _FORCE_INLINE_ void set_language_index(int p_idx) { lang_idx = p_idx; } + #ifndef NO_THREADS virtual void thread_enter(); virtual void thread_exit(); @@ -293,6 +315,14 @@ public: virtual void profiling_stop(); virtual int profiling_get_accumulated_data(ProfilingInfo *p_info_arr, int p_info_max); virtual int profiling_get_frame_data(ProfilingInfo *p_info_arr, int p_info_max); + + int register_binding_functions(godot_instance_binding_functions p_binding_functions); + void unregister_binding_functions(int p_idx); + + void *get_instance_binding_data(int p_idx, Object *p_object); + + virtual void *alloc_instance_binding_data(Object *p_object); + virtual void free_instance_binding_data(void *p_data); }; inline NativeScriptDesc *NativeScript::get_script_desc() const { diff --git a/modules/gdnative/nativescript/register_types.cpp b/modules/gdnative/nativescript/register_types.cpp index cb55a13b3e..9a0e764391 100644 --- a/modules/gdnative/nativescript/register_types.cpp +++ b/modules/gdnative/nativescript/register_types.cpp @@ -47,6 +47,7 @@ void register_nativescript_types() { ClassDB::register_class<NativeScript>(); + native_script_language->set_language_index(ScriptServer::get_language_count()); ScriptServer::register_language(native_script_language); resource_saver_gdns = memnew(ResourceFormatSaverNativeScript); diff --git a/modules/mono/SCsub b/modules/mono/SCsub index 320bbe7090..aa8626e6da 100644 --- a/modules/mono/SCsub +++ b/modules/mono/SCsub @@ -57,10 +57,10 @@ if env['tools']: vars = Variables() vars.Add(BoolVariable('mono_glue', 'Build with the mono glue sources', True)) vars.Add(BoolVariable('xbuild_fallback', 'If MSBuild is not found, fallback to xbuild', False)) -vars.Update(env) +vars.Update(env_mono) # Glue sources -if env['mono_glue']: +if env_mono['mono_glue']: env_mono.add_source_files(env.modules_sources, 'glue/*.cpp') else: env_mono.Append(CPPDEFINES=['MONO_GLUE_DISABLED']) diff --git a/modules/openssl/stream_peer_openssl.cpp b/modules/openssl/stream_peer_openssl.cpp index e3cb9bbdf8..84c4e85006 100644 --- a/modules/openssl/stream_peer_openssl.cpp +++ b/modules/openssl/stream_peer_openssl.cpp @@ -141,9 +141,6 @@ int StreamPeerOpenSSL::_cert_verify_callback(X509_STORE_CTX *x509_ctx, void *arg X509_NAME_oneline(X509_get_subject_name(server_cert), cert_str, sizeof(cert_str)); - print_line("CERT STR: " + String(cert_str)); - print_line("VALID: " + itos(base_cert_valid)); - if (!base_cert_valid) return 0; @@ -382,7 +379,6 @@ Error StreamPeerOpenSSL::connect_to_stream(Ref<StreamPeer> p_base, bool p_valida // Same as before, try to connect. int result = SSL_connect(ssl); - print_line("CONNECTION RESULT: " + itos(result)); if (result < 1) { ERR_print_errors_fp(stdout); _print_error(result); @@ -392,7 +388,6 @@ Error StreamPeerOpenSSL::connect_to_stream(Ref<StreamPeer> p_base, bool p_valida if (peer) { bool cert_ok = SSL_get_verify_result(ssl) == X509_V_OK; - print_line("cert_ok: " + itos(cert_ok)); } else if (validate_certs) { status = STATUS_ERROR_NO_CERTIFICATE; diff --git a/modules/websocket/SCsub b/modules/websocket/SCsub new file mode 100644 index 0000000000..067a99ffff --- /dev/null +++ b/modules/websocket/SCsub @@ -0,0 +1,70 @@ +#!/usr/bin/env python + +Import('env') +Import('env_modules') + +# Thirdparty source files + +env_lws = env_modules.Clone() + +thirdparty_dir = "#thirdparty/lws/" +helper_dir = "win32helpers/" +openssl_dir = "#thirdparty/openssl/" +thirdparty_sources = [ + "client/client.c", + "client/client-handshake.c", + "client/client-parser.c", + "client/ssl-client.c", + + "ext/extension.c", + "ext/extension-permessage-deflate.c", + + "server/fops-zip.c", + "server/lejp-conf.c", + "server/parsers.c", + "server/ranges.c", + "server/server.c", + "server/server-handshake.c", + "server/ssl-server.c", + + "misc/base64-decode.c", + "misc/lejp.c", + "misc/sha-1.c", + + "alloc.c", + "context.c", + "handshake.c", + "header.c", + "libwebsockets.c", + "minilex.c", + "output.c", + "pollfd.c", + "service.c", + "ssl.c", + +] + +if env_lws["platform"] == "android": # Builtin getifaddrs + thirdparty_sources += ["misc/getifaddrs.c"] + +if env_lws["platform"] == "windows": # Winsock + thirdparty_sources += ["plat/lws-plat-win.c", helper_dir + "getopt.c", helper_dir + "getopt_long.c", helper_dir + "gettimeofday.c"] +else: # Unix socket + thirdparty_sources += ["plat/lws-plat-unix.c"] + + +thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] + +if env_lws["platform"] == "javascript": # No need to add third party libraries at all + pass +else: + env_lws.add_source_files(env.modules_sources, thirdparty_sources) + env_lws.Append(CPPPATH=[thirdparty_dir]) + + if env['builtin_openssl']: + env_lws.Append(CPPPATH=[openssl_dir]) + + if env_lws["platform"] == "windows": + env_lws.Append(CPPPATH=[thirdparty_dir + helper_dir]) + +env_lws.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/websocket/config.py b/modules/websocket/config.py new file mode 100644 index 0000000000..fb920482f5 --- /dev/null +++ b/modules/websocket/config.py @@ -0,0 +1,7 @@ + +def can_build(platform): + return True + + +def configure(env): + pass diff --git a/modules/websocket/emws_client.cpp b/modules/websocket/emws_client.cpp new file mode 100644 index 0000000000..38fe520fc1 --- /dev/null +++ b/modules/websocket/emws_client.cpp @@ -0,0 +1,224 @@ +/*************************************************************************/ +/* emws_client.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#ifdef JAVASCRIPT_ENABLED + +#include "emws_client.h" +#include "core/io/ip.h" +#include "emscripten.h" + +extern "C" { +EMSCRIPTEN_KEEPALIVE void _esws_on_connect(void *obj, char *proto) { + EMWSClient *client = static_cast<EMWSClient *>(obj); + client->_is_connecting = false; + client->_on_connect(String(proto)); +} + +EMSCRIPTEN_KEEPALIVE void _esws_on_message(void *obj, uint8_t *p_data, int p_data_size, int p_is_string) { + EMWSClient *client = static_cast<EMWSClient *>(obj); + + static_cast<EMWSPeer *>(*client->get_peer(1))->read_msg(p_data, p_data_size, p_is_string == 1); + client->_on_peer_packet(); +} + +EMSCRIPTEN_KEEPALIVE void _esws_on_error(void *obj) { + EMWSClient *client = static_cast<EMWSClient *>(obj); + client->_is_connecting = false; + client->_on_error(); +} + +EMSCRIPTEN_KEEPALIVE void _esws_on_close(void *obj, int code, char *reason, int was_clean) { + EMWSClient *client = static_cast<EMWSClient *>(obj); + client->_is_connecting = false; + client->_on_disconnect(); +} +} + +Error EMWSClient::connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, PoolVector<String> p_protocols) { + + String str = "ws://"; + String proto_string = ""; + int i = 0; + + if (p_ssl) + str = "wss://"; + str += p_host + ":" + itos(p_port) + p_path; + for (int i = 0; i < p_protocols.size(); i++) { + proto_string += p_protocols[i]; + proto_string += ","; + } + if (proto_string == "") + proto_string = "binary,"; + + proto_string = proto_string.substr(0, proto_string.length() - 1); + + _is_connecting = true; + /* clang-format off */ + int peer_sock = EM_ASM_INT({ + var socket = new WebSocket(UTF8ToString($1), UTF8ToString($2).split(",")); + var c_ptr = Module.IDHandler.get($0); + socket.binaryType = "arraybuffer"; + + // Connection opened + socket.addEventListener("open", function (event) { + if (!Module.IDHandler.has($0)) + return; // Godot Object is gone! + ccall("_esws_on_connect", + "void", + ["number", "string"], + [c_ptr, socket.protocol] + ); + }); + + // Listen for messages + socket.addEventListener("message", function (event) { + if (!Module.IDHandler.has($0)) + return; // Godot Object is gone! + var buffer; + var is_string = 0; + if (event.data instanceof ArrayBuffer) { + + buffer = new Uint8Array(event.data); + + } else if (event.data instanceof Blob) { + + alert("Blob type not supported"); + return; + + } else if (typeof event.data === "string") { + + is_string = 1; + var enc = new TextEncoder("utf-8"); + buffer = new Uint8Array(enc.encode(event.data)); + + } else { + + alert("Unknown message type"); + return; + + } + var len = buffer.length*buffer.BYTES_PER_ELEMENT; + var out = Module._malloc(len); + Module.HEAPU8.set(buffer, out); + ccall("_esws_on_message", + "void", + ["number", "number", "number", "number"], + [c_ptr, out, len, is_string] + ); + Module._free(out); + }); + + socket.addEventListener("error", function (event) { + if (!Module.IDHandler.has($0)) + return; // Godot Object is gone! + ccall("_esws_on_error", + "void", + ["number"], + [c_ptr] + ); + }); + + socket.addEventListener("close", function (event) { + if (!Module.IDHandler.has($0)) + return; // Godot Object is gone! + var was_clean = 0; + if (event.was_clean) + was_clean = 1; + ccall("_esws_on_close", + "void", + ["number", "number", "string", "number"], + [c_ptr, event.code, event.reason, was_clean] + ); + }); + + return Module.IDHandler.add(socket); + }, _js_id, str.utf8().get_data(), proto_string.utf8().get_data()); + /* clang-format on */ + + static_cast<Ref<EMWSPeer> >(_peer)->set_sock(peer_sock); + + return OK; +}; + +void EMWSClient::poll() { +} + +Ref<WebSocketPeer> EMWSClient::get_peer(int p_peer_id) const { + + return _peer; +} + +NetworkedMultiplayerPeer::ConnectionStatus EMWSClient::get_connection_status() const { + + if (_peer->is_connected_to_host()) + return CONNECTION_CONNECTED; + + if (_is_connecting) + return CONNECTION_CONNECTING; + + return CONNECTION_DISCONNECTED; +}; + +void EMWSClient::disconnect_from_host() { + + _peer->close(); +}; + +IP_Address EMWSClient::get_connected_host() const { + + return IP_Address(); +}; + +uint16_t EMWSClient::get_connected_port() const { + + return 1025; +}; + +EMWSClient::EMWSClient() { + _is_connecting = false; + _peer = Ref<EMWSPeer>(memnew(EMWSPeer)); + /* clang-format off */ + _js_id = EM_ASM_INT({ + return Module.IDHandler.add($0); + }, this); + /* clang-format on */ +}; + +EMWSClient::~EMWSClient() { + + disconnect_from_host(); + _peer = Ref<EMWSPeer>(); + /* clang-format off */ + EM_ASM({ + Module.IDHandler.remove($0); + }, _js_id); + /* clang-format on */ +}; + +#endif // JAVASCRIPT_ENABLED diff --git a/modules/websocket/emws_client.h b/modules/websocket/emws_client.h new file mode 100644 index 0000000000..8801f37007 --- /dev/null +++ b/modules/websocket/emws_client.h @@ -0,0 +1,62 @@ +/*************************************************************************/ +/* emws_client.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#ifndef EMWSCLIENT_H +#define EMWSCLIENT_H + +#ifdef JAVASCRIPT_ENABLED + +#include "core/error_list.h" +#include "emws_peer.h" +#include "websocket_client.h" + +class EMWSClient : public WebSocketClient { + + GDCIIMPL(EMWSClient, WebSocketClient); + +private: + int _js_id; + +public: + bool _is_connecting; + + Error connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, PoolVector<String> p_protocol = PoolVector<String>()); + Ref<WebSocketPeer> get_peer(int p_peer_id) const; + void disconnect_from_host(); + IP_Address get_connected_host() const; + uint16_t get_connected_port() const; + virtual ConnectionStatus get_connection_status() const; + virtual void poll(); + EMWSClient(); + ~EMWSClient(); +}; + +#endif // JAVASCRIPT_ENABLED + +#endif // EMWSCLIENT_H diff --git a/modules/websocket/emws_peer.cpp b/modules/websocket/emws_peer.cpp new file mode 100644 index 0000000000..93665e6428 --- /dev/null +++ b/modules/websocket/emws_peer.cpp @@ -0,0 +1,173 @@ +/*************************************************************************/ +/* emws_peer.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#ifdef JAVASCRIPT_ENABLED + +#include "emws_peer.h" +#include "core/io/ip.h" + +void EMWSPeer::set_sock(int p_sock) { + + peer_sock = p_sock; + in_buffer.clear(); + queue_count = 0; +} + +void EMWSPeer::set_write_mode(WriteMode p_mode) { + write_mode = p_mode; +} + +EMWSPeer::WriteMode EMWSPeer::get_write_mode() const { + return write_mode; +} + +void EMWSPeer::read_msg(uint8_t *p_data, uint32_t p_size, bool p_is_string) { + + if (in_buffer.space_left() < p_size + 5) { + ERR_EXPLAIN("Buffer full! Dropping data"); + ERR_FAIL(); + } + + uint8_t is_string = p_is_string ? 1 : 0; + in_buffer.write((uint8_t *)&p_size, 4); + in_buffer.write((uint8_t *)&is_string, 1); + in_buffer.write(p_data, p_size); + queue_count++; +} + +Error EMWSPeer::put_packet(const uint8_t *p_buffer, int p_buffer_size) { + + int is_bin = write_mode == WebSocketPeer::WRITE_MODE_BINARY ? 1 : 0; + + /* clang-format off */ + EM_ASM({ + var sock = Module.IDHandler.get($0); + var bytes_array = new Uint8Array($2); + var i = 0; + + for(i=0; i<$2; i++) { + bytes_array[i] = getValue($1+i, 'i8'); + } + + if ($3) { + sock.send(bytes_array.buffer); + } else { + var string = new TextDecoder("utf-8").decode(bytes_array); + sock.send(string); + } + }, peer_sock, p_buffer, p_buffer_size, is_bin); + /* clang-format on */ + + return OK; +}; + +Error EMWSPeer::get_packet(const uint8_t **r_buffer, int &r_buffer_size) { + + if (queue_count == 0) + return ERR_UNAVAILABLE; + + uint32_t to_read = 0; + uint32_t left = 0; + uint8_t is_string = 0; + r_buffer_size = 0; + + in_buffer.read((uint8_t *)&to_read, 4); + --queue_count; + left = in_buffer.data_left(); + + if (left < to_read + 1) { + in_buffer.advance_read(left); + return FAILED; + } + + in_buffer.read(&is_string, 1); + _was_string = is_string == 1; + in_buffer.read(packet_buffer, to_read); + *r_buffer = packet_buffer; + r_buffer_size = to_read; + + return OK; +}; + +int EMWSPeer::get_available_packet_count() const { + + return queue_count; +}; + +bool EMWSPeer::was_string_packet() const { + + return _was_string; +}; + +bool EMWSPeer::is_connected_to_host() const { + + return peer_sock != -1; +}; + +void EMWSPeer::close() { + + if (peer_sock != -1) { + /* clang-format off */ + EM_ASM({ + var sock = Module.IDHandler.get($0); + sock.close(); + Module.IDHandler.remove($0); + }, peer_sock); + /* clang-format on */ + } + peer_sock = -1; + queue_count = 0; + in_buffer.clear(); +}; + +IP_Address EMWSPeer::get_connected_host() const { + + return IP_Address(); +}; + +uint16_t EMWSPeer::get_connected_port() const { + + return 1025; +}; + +EMWSPeer::EMWSPeer() { + peer_sock = -1; + queue_count = 0; + _was_string = false; + in_buffer.resize(16); + write_mode = WRITE_MODE_BINARY; +}; + +EMWSPeer::~EMWSPeer() { + + in_buffer.resize(0); + close(); +}; + +#endif // JAVASCRIPT_ENABLED diff --git a/modules/websocket/emws_peer.h b/modules/websocket/emws_peer.h new file mode 100644 index 0000000000..a50d1874ba --- /dev/null +++ b/modules/websocket/emws_peer.h @@ -0,0 +1,85 @@ +/*************************************************************************/ +/* emws_peer.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#ifndef EMWSPEER_H +#define EMWSPEER_H + +#ifdef JAVASCRIPT_ENABLED + +#include "core/error_list.h" +#include "core/io/packet_peer.h" +#include "core/ring_buffer.h" +#include "emscripten.h" +#include "websocket_peer.h" + +class EMWSPeer : public WebSocketPeer { + + GDCIIMPL(EMWSPeer, WebSocketPeer); + +private: + enum { + PACKET_BUFFER_SIZE = 65536 - 5 // 4 bytes for the size, 1 for for type + }; + + int peer_sock; + WriteMode write_mode; + + uint8_t packet_buffer[PACKET_BUFFER_SIZE]; + RingBuffer<uint8_t> in_buffer; + int queue_count; + bool _was_string; + +public: + void read_msg(uint8_t *p_data, uint32_t p_size, bool p_is_string); + void set_sock(int sock); + virtual int get_available_packet_count() const; + virtual Error get_packet(const uint8_t **r_buffer, int &r_buffer_size); + virtual Error put_packet(const uint8_t *p_buffer, int p_buffer_size); + virtual int get_max_packet_size() const { return PACKET_BUFFER_SIZE; }; + + virtual void close(); + virtual bool is_connected_to_host() const; + virtual IP_Address get_connected_host() const; + virtual uint16_t get_connected_port() const; + + virtual WriteMode get_write_mode() const; + virtual void set_write_mode(WriteMode p_mode); + virtual bool was_string_packet() const; + + void set_wsi(struct lws *wsi); + Error read_wsi(void *in, size_t len); + Error write_wsi(); + + EMWSPeer(); + ~EMWSPeer(); +}; + +#endif // JAVASCRIPT_ENABLED + +#endif // LSWPEER_H diff --git a/modules/websocket/emws_server.cpp b/modules/websocket/emws_server.cpp new file mode 100644 index 0000000000..60e9133225 --- /dev/null +++ b/modules/websocket/emws_server.cpp @@ -0,0 +1,67 @@ +/*************************************************************************/ +/* emws_server.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#ifdef JAVASCRIPT_ENABLED + +#include "emws_server.h" +#include "core/os/os.h" + +Error EMWSServer::listen(int p_port, PoolVector<String> p_protocols, bool gd_mp_api) { + + return FAILED; +} + +bool EMWSServer::is_listening() const { + return false; +} + +void EMWSServer::stop() { +} + +bool EMWSServer::has_peer(int p_id) const { + return false; +} + +Ref<WebSocketPeer> EMWSServer::get_peer(int p_id) const { + return NULL; +} + +PoolVector<String> EMWSServer::get_protocols() const { + PoolVector<String> out; + + return out; +} + +EMWSServer::EMWSServer() { +} + +EMWSServer::~EMWSServer() { +} + +#endif // JAVASCRIPT_ENABLED diff --git a/modules/websocket/emws_server.h b/modules/websocket/emws_server.h new file mode 100644 index 0000000000..59f1d76346 --- /dev/null +++ b/modules/websocket/emws_server.h @@ -0,0 +1,58 @@ +/*************************************************************************/ +/* emws_server.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#ifndef EMWSSERVER_H +#define EMWSSERVER_H + +#ifdef JAVASCRIPT_ENABLED + +#include "core/reference.h" +#include "emws_peer.h" +#include "websocket_server.h" + +class EMWSServer : public WebSocketServer { + + GDCIIMPL(EMWSServer, WebSocketServer); + +public: + Error listen(int p_port, PoolVector<String> p_protocols = PoolVector<String>(), bool gd_mp_api = false); + void stop(); + bool is_listening() const; + bool has_peer(int p_id) const; + Ref<WebSocketPeer> get_peer(int p_id) const; + virtual void poll(); + virtual PoolVector<String> get_protocols() const; + + EMWSServer(); + ~EMWSServer(); +}; + +#endif + +#endif // LWSSERVER_H diff --git a/modules/websocket/lws_client.cpp b/modules/websocket/lws_client.cpp new file mode 100644 index 0000000000..604b1886ad --- /dev/null +++ b/modules/websocket/lws_client.cpp @@ -0,0 +1,203 @@ +/*************************************************************************/ +/* lws_client.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#ifndef JAVASCRIPT_ENABLED + +#include "lws_client.h" +#include "core/io/ip.h" + +Error LWSClient::connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, PoolVector<String> p_protocols) { + + ERR_FAIL_COND_V(context != NULL, FAILED); + + IP_Address addr; + + if (!p_host.is_valid_ip_address()) { + addr = IP::get_singleton()->resolve_hostname(p_host); + } else { + addr = p_host; + } + + ERR_FAIL_COND_V(!addr.is_valid(), ERR_INVALID_PARAMETER); + + // prepare protocols + if (p_protocols.size() == 0) // default to binary protocol + p_protocols.append("binary"); + _lws_make_protocols(this, &LWSClient::_lws_gd_callback, p_protocols, &_lws_ref); + + // init lws client + struct lws_context_creation_info info; + struct lws_client_connect_info i; + + memset(&i, 0, sizeof i); + memset(&info, 0, sizeof info); + + info.port = CONTEXT_PORT_NO_LISTEN; + info.protocols = _lws_ref->lws_structs; + info.gid = -1; + info.uid = -1; + //info.ws_ping_pong_interval = 5; + info.user = _lws_ref; + context = lws_create_context(&info); + + if (context == NULL) { + _lws_free_ref(_lws_ref); + _lws_ref = NULL; + ERR_EXPLAIN("Unable to create lws context"); + ERR_FAIL_V(FAILED); + } + + char abuf[1024]; + char hbuf[1024]; + char pbuf[2048]; + String addr_str = (String)addr; + strncpy(abuf, addr_str.ascii().get_data(), 1024); + strncpy(hbuf, p_host.utf8().get_data(), 1024); + strncpy(pbuf, p_path.utf8().get_data(), 2048); + + i.context = context; + i.protocol = _lws_ref->lws_names; + i.address = abuf; + i.host = hbuf; + i.path = pbuf; + i.port = p_port; + i.ssl_connection = p_ssl; + + lws_client_connect_via_info(&i); + return OK; +}; + +void LWSClient::poll() { + + _lws_poll(); +} + +int LWSClient::_handle_cb(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { + + Ref<LWSPeer> peer = static_cast<Ref<LWSPeer> >(_peer); + LWSPeer::PeerData *peer_data = (LWSPeer::PeerData *)user; + + switch (reason) { + + case LWS_CALLBACK_CLIENT_ESTABLISHED: + peer->set_wsi(wsi); + peer_data->peer_id = 0; + peer_data->in_size = 0; + peer_data->in_count = 0; + peer_data->out_count = 0; + peer_data->rbw.resize(16); + peer_data->rbr.resize(16); + peer_data->force_close = false; + _on_connect(lws_get_protocol(wsi)->name); + break; + + case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: + _on_error(); + destroy_context(); + return -1; // we should close the connection (would probably happen anyway) + + case LWS_CALLBACK_CLOSED: + peer_data->in_count = 0; + peer_data->out_count = 0; + peer_data->rbw.resize(0); + peer_data->rbr.resize(0); + peer->close(); + destroy_context(); + _on_disconnect(); + return 0; // we can end here + + case LWS_CALLBACK_CLIENT_RECEIVE: + peer->read_wsi(in, len); + if (peer->get_available_packet_count() > 0) + _on_peer_packet(); + break; + + case LWS_CALLBACK_CLIENT_WRITEABLE: + if (peer_data->force_close) + return -1; + + peer->write_wsi(); + break; + + default: + break; + } + + return 0; +} + +Ref<WebSocketPeer> LWSClient::get_peer(int p_peer_id) const { + + return _peer; +} + +NetworkedMultiplayerPeer::ConnectionStatus LWSClient::get_connection_status() const { + + if (context == NULL) + return CONNECTION_DISCONNECTED; + + if (_peer->is_connected_to_host()) + return CONNECTION_CONNECTED; + + return CONNECTION_CONNECTING; +} + +void LWSClient::disconnect_from_host() { + + if (context == NULL) + return; + + _peer->close(); + destroy_context(); +}; + +IP_Address LWSClient::get_connected_host() const { + + return IP_Address(); +}; + +uint16_t LWSClient::get_connected_port() const { + + return 1025; +}; + +LWSClient::LWSClient() { + context = NULL; + _lws_ref = NULL; + _peer = Ref<LWSPeer>(memnew(LWSPeer)); +}; + +LWSClient::~LWSClient() { + + invalidate_lws_ref(); // We do not want any more callback + disconnect_from_host(); + _peer = Ref<LWSPeer>(); +}; + +#endif // JAVASCRIPT_ENABLED diff --git a/modules/websocket/lws_client.h b/modules/websocket/lws_client.h new file mode 100644 index 0000000000..2e082175df --- /dev/null +++ b/modules/websocket/lws_client.h @@ -0,0 +1,61 @@ +/*************************************************************************/ +/* lws_client.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#ifndef LWSCLIENT_H +#define LWSCLIENT_H + +#ifndef JAVASCRIPT_ENABLED + +#include "core/error_list.h" +#include "lws_helper.h" +#include "lws_peer.h" +#include "websocket_client.h" + +class LWSClient : public WebSocketClient { + + GDCIIMPL(LWSClient, WebSocketClient); + + LWS_HELPER(LWSClient); + +public: + Error connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, PoolVector<String> p_protocol = PoolVector<String>()); + Ref<WebSocketPeer> get_peer(int p_peer_id) const; + void disconnect_from_host(); + IP_Address get_connected_host() const; + uint16_t get_connected_port() const; + virtual ConnectionStatus get_connection_status() const; + virtual void poll(); + + LWSClient(); + ~LWSClient(); +}; + +#endif // JAVASCRIPT_ENABLED + +#endif // LWSCLIENT_H diff --git a/modules/websocket/lws_helper.h b/modules/websocket/lws_helper.h new file mode 100644 index 0000000000..ac0c340aa9 --- /dev/null +++ b/modules/websocket/lws_helper.h @@ -0,0 +1,214 @@ +/*************************************************************************/ +/* lws_helper.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#ifndef LWS_HELPER_H +#define LWS_HELPER_H + +#include "core/io/stream_peer.h" +#include "core/os/os.h" +#include "core/reference.h" +#include "core/ring_buffer.h" +#include "lws_peer.h" + +struct _LWSRef { + bool free_context; + bool is_polling; + bool is_valid; + bool is_destroying; + void *obj; + struct lws_protocols *lws_structs; + char *lws_names; +}; + +static _LWSRef *_lws_create_ref(void *obj) { + + _LWSRef *out = (_LWSRef *)memalloc(sizeof(_LWSRef)); + out->is_destroying = false; + out->free_context = false; + out->is_polling = false; + out->obj = obj; + out->is_valid = true; + out->lws_structs = NULL; + out->lws_names = NULL; + return out; +} + +static void _lws_free_ref(_LWSRef *ref) { + // Free strings and structs + memfree(ref->lws_structs); + memfree(ref->lws_names); + // Free ref + memfree(ref); +} + +static bool _lws_destroy(struct lws_context *context, _LWSRef *ref) { + if (context == NULL || ref->is_destroying) + return false; + + if (ref->is_polling) { + ref->free_context = true; + return false; + } + + ref->is_destroying = true; + lws_context_destroy(context); + _lws_free_ref(ref); + return true; +} + +static bool _lws_poll(struct lws_context *context, _LWSRef *ref) { + + ERR_FAIL_COND_V(context == NULL, false); + ERR_FAIL_COND_V(ref == NULL, false); + + ref->is_polling = true; + lws_service(context, 0); + ref->is_polling = false; + + if (!ref->free_context) + return false; // Nothing to do + + bool is_valid = ref->is_valid; // Might have been destroyed by poll + + _lws_destroy(context, ref); // Will destroy context and ref + + return is_valid; // If the object should NULL its context and ref +} + +/* + * prepare the protocol_structs to be fed to context + * also prepare the protocol string used by the client + */ +static void _lws_make_protocols(void *p_obj, lws_callback_function *p_callback, PoolVector<String> p_names, _LWSRef **r_lws_ref) { + /* the input strings might go away after this call, + * we need to copy them. Will clear them when + * detroying the context */ + int i; + int len = p_names.size(); + size_t data_size = sizeof(struct LWSPeer::PeerData); + PoolVector<String>::Read pnr = p_names.read(); + + /* + * This is a reference connecting the object with lws + * keep track of status, mallocs, etc. + * Must survive as long the context + * Must be freed manually when context creation fails. + */ + _LWSRef *ref = _lws_create_ref(p_obj); + + /* LWS protocol structs */ + ref->lws_structs = (struct lws_protocols *)memalloc(sizeof(struct lws_protocols) * (len + 2)); + + CharString strings = p_names.join(",").ascii(); + int str_len = strings.length(); + + /* Joined string of protocols, double the size: comma separated first, NULL separated last */ + ref->lws_names = (char *)memalloc((str_len + 1) * 2); /* plus the terminator */ + + char *names_ptr = ref->lws_names; + struct lws_protocols *structs_ptr = ref->lws_structs; + + copymem(names_ptr, strings.get_data(), str_len); + names_ptr[str_len] = '\0'; /* NULL terminator */ + /* NULL terminated strings to be used in protocol structs */ + copymem(&names_ptr[str_len + 1], strings.get_data(), str_len); + names_ptr[(str_len * 2) + 1] = '\0'; /* NULL terminator */ + int pos = str_len + 1; + + /* the first protocol is always http-only */ + structs_ptr[0].name = "http-only"; + structs_ptr[0].callback = p_callback; + structs_ptr[0].per_session_data_size = data_size; + structs_ptr[0].rx_buffer_size = 0; + /* add user defined protocols */ + for (i = 0; i < len; i++) { + structs_ptr[i + 1].name = (const char *)&names_ptr[pos]; + structs_ptr[i + 1].callback = p_callback; + structs_ptr[i + 1].per_session_data_size = data_size; + structs_ptr[i + 1].rx_buffer_size = 0; + pos += pnr[i].ascii().length() + 1; + names_ptr[pos - 1] = '\0'; + } + /* add protocols terminator */ + structs_ptr[len + 1].name = NULL; + structs_ptr[len + 1].callback = NULL; + structs_ptr[len + 1].per_session_data_size = 0; + structs_ptr[len + 1].rx_buffer_size = 0; + + *r_lws_ref = ref; +} + +/* clang-format off */ +#define LWS_HELPER(CNAME) \ +protected: \ + struct _LWSRef *_lws_ref; \ + struct lws_context *context; \ + \ + static int _lws_gd_callback(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { \ + \ + if (wsi == NULL) { \ + return 0; \ + } \ + \ + struct _LWSRef *ref = (struct _LWSRef *)lws_context_user(lws_get_context(wsi)); \ + if (!ref->is_valid) \ + return 0; \ + CNAME *helper = (CNAME *)ref->obj; \ + return helper->_handle_cb(wsi, reason, user, in, len); \ + } \ + \ + void invalidate_lws_ref() { \ + if (_lws_ref != NULL) \ + _lws_ref->is_valid = false; \ + } \ + \ + void destroy_context() { \ + if (_lws_destroy(context, _lws_ref)) { \ + context = NULL; \ + _lws_ref = NULL; \ + } \ + } \ + \ +public: \ + virtual int _handle_cb(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len); \ + \ + void _lws_poll() { \ + ERR_FAIL_COND(context == NULL); \ + \ + if (::_lws_poll(context, _lws_ref)) { \ + context = NULL; \ + _lws_ref = NULL; \ + } \ + } \ + \ +protected: + + /* clang-format on */ + +#endif // LWS_HELPER_H diff --git a/modules/websocket/lws_peer.cpp b/modules/websocket/lws_peer.cpp new file mode 100644 index 0000000000..fdaa79f9d4 --- /dev/null +++ b/modules/websocket/lws_peer.cpp @@ -0,0 +1,200 @@ +/*************************************************************************/ +/* lws_peer.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#ifndef JAVASCRIPT_ENABLED + +#include "lws_peer.h" +#include "core/io/ip.h" + +void LWSPeer::set_wsi(struct lws *p_wsi) { + wsi = p_wsi; +}; + +void LWSPeer::set_write_mode(WriteMode p_mode) { + write_mode = p_mode; +} + +LWSPeer::WriteMode LWSPeer::get_write_mode() const { + return write_mode; +} + +Error LWSPeer::read_wsi(void *in, size_t len) { + + ERR_FAIL_COND_V(!is_connected_to_host(), FAILED); + + PeerData *peer_data = (PeerData *)(lws_wsi_user(wsi)); + uint32_t size = peer_data->in_size; + uint8_t is_string = lws_frame_is_binary(wsi) ? 0 : 1; + + if (peer_data->rbr.space_left() < len + 5) { + ERR_EXPLAIN("Buffer full! Dropping data"); + ERR_FAIL_V(FAILED); + } + + copymem(&(peer_data->input_buffer[size]), in, len); + size += len; + + peer_data->in_size = size; + if (lws_is_final_fragment(wsi)) { + peer_data->rbr.write((uint8_t *)&size, 4); + peer_data->rbr.write((uint8_t *)&is_string, 1); + peer_data->rbr.write(peer_data->input_buffer, size); + peer_data->in_count++; + peer_data->in_size = 0; + } + + return OK; +} + +Error LWSPeer::write_wsi() { + + ERR_FAIL_COND_V(!is_connected_to_host(), FAILED); + + PeerData *peer_data = (PeerData *)(lws_wsi_user(wsi)); + PoolVector<uint8_t> tmp; + int left = peer_data->rbw.data_left(); + uint32_t to_write = 0; + + if (left == 0 || peer_data->out_count == 0) + return OK; + + peer_data->rbw.read((uint8_t *)&to_write, 4); + peer_data->out_count--; + + if (left < to_write) { + peer_data->rbw.advance_read(left); + return FAILED; + } + + tmp.resize(LWS_PRE + to_write); + peer_data->rbw.read(&(tmp.write()[LWS_PRE]), to_write); + lws_write(wsi, &(tmp.write()[LWS_PRE]), to_write, (enum lws_write_protocol)write_mode); + tmp.resize(0); + + if (peer_data->out_count > 0) + lws_callback_on_writable(wsi); // we want to write more! + + return OK; +} + +Error LWSPeer::put_packet(const uint8_t *p_buffer, int p_buffer_size) { + + ERR_FAIL_COND_V(!is_connected_to_host(), FAILED); + + PeerData *peer_data = (PeerData *)lws_wsi_user(wsi); + peer_data->rbw.write((uint8_t *)&p_buffer_size, 4); + peer_data->rbw.write(p_buffer, MIN(p_buffer_size, peer_data->rbw.space_left())); + peer_data->out_count++; + + lws_callback_on_writable(wsi); // notify that we want to write + return OK; +}; + +Error LWSPeer::get_packet(const uint8_t **r_buffer, int &r_buffer_size) { + + ERR_FAIL_COND_V(!is_connected_to_host(), FAILED); + + PeerData *peer_data = (PeerData *)lws_wsi_user(wsi); + + if (peer_data->in_count == 0) + return ERR_UNAVAILABLE; + + uint32_t to_read = 0; + uint32_t left = 0; + uint8_t is_string = 0; + r_buffer_size = 0; + + peer_data->rbr.read((uint8_t *)&to_read, 4); + peer_data->in_count--; + left = peer_data->rbr.data_left(); + + if (left < to_read + 1) { + peer_data->rbr.advance_read(left); + return FAILED; + } + + peer_data->rbr.read(&is_string, 1); + peer_data->rbr.read(packet_buffer, to_read); + *r_buffer = packet_buffer; + r_buffer_size = to_read; + _was_string = is_string; + + return OK; +}; + +int LWSPeer::get_available_packet_count() const { + + if (!is_connected_to_host()) + return 0; + + return ((PeerData *)lws_wsi_user(wsi))->in_count; +}; + +bool LWSPeer::was_string_packet() const { + + return _was_string; +}; + +bool LWSPeer::is_connected_to_host() const { + + return wsi != NULL; +}; + +void LWSPeer::close() { + if (wsi != NULL) { + struct lws *tmp = wsi; + PeerData *data = ((PeerData *)lws_wsi_user(wsi)); + data->force_close = true; + wsi = NULL; + lws_callback_on_writable(tmp); // notify that we want to disconnect + } +}; + +IP_Address LWSPeer::get_connected_host() const { + + return IP_Address(); +}; + +uint16_t LWSPeer::get_connected_port() const { + + return 1025; +}; + +LWSPeer::LWSPeer() { + wsi = NULL; + _was_string = false; + write_mode = WRITE_MODE_BINARY; +}; + +LWSPeer::~LWSPeer() { + + close(); +}; + +#endif // JAVASCRIPT_ENABLED diff --git a/modules/websocket/lws_peer.h b/modules/websocket/lws_peer.h new file mode 100644 index 0000000000..0a62b65d24 --- /dev/null +++ b/modules/websocket/lws_peer.h @@ -0,0 +1,92 @@ +/*************************************************************************/ +/* lws_peer.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#ifndef LWSPEER_H +#define LWSPEER_H + +#ifndef JAVASCRIPT_ENABLED + +#include "core/error_list.h" +#include "core/io/packet_peer.h" +#include "core/ring_buffer.h" +#include "libwebsockets.h" +#include "lws_config.h" +#include "websocket_peer.h" + +class LWSPeer : public WebSocketPeer { + + GDCIIMPL(LWSPeer, WebSocketPeer); + +private: + enum { + PACKET_BUFFER_SIZE = 65536 - 5 // 4 bytes for the size, 1 for the type + }; + + uint8_t packet_buffer[PACKET_BUFFER_SIZE]; + struct lws *wsi; + WriteMode write_mode; + bool _was_string; + +public: + struct PeerData { + uint32_t peer_id; + bool force_close; + RingBuffer<uint8_t> rbw; + RingBuffer<uint8_t> rbr; + mutable uint8_t input_buffer[PACKET_BUFFER_SIZE]; + uint32_t in_size; + int in_count; + int out_count; + }; + + virtual int get_available_packet_count() const; + virtual Error get_packet(const uint8_t **r_buffer, int &r_buffer_size); + virtual Error put_packet(const uint8_t *p_buffer, int p_buffer_size); + virtual int get_max_packet_size() const { return PACKET_BUFFER_SIZE; }; + + virtual void close(); + virtual bool is_connected_to_host() const; + virtual IP_Address get_connected_host() const; + virtual uint16_t get_connected_port() const; + + virtual WriteMode get_write_mode() const; + virtual void set_write_mode(WriteMode p_mode); + virtual bool was_string_packet() const; + + void set_wsi(struct lws *wsi); + Error read_wsi(void *in, size_t len); + Error write_wsi(); + + LWSPeer(); + ~LWSPeer(); +}; + +#endif // JAVASCRIPT_ENABLED + +#endif // LSWPEER_H diff --git a/modules/websocket/lws_server.cpp b/modules/websocket/lws_server.cpp new file mode 100644 index 0000000000..8a47ba557d --- /dev/null +++ b/modules/websocket/lws_server.cpp @@ -0,0 +1,177 @@ +/*************************************************************************/ +/* lws_server.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#ifndef JAVASCRIPT_ENABLED + +#include "lws_server.h" +#include "core/os/os.h" + +Error LWSServer::listen(int p_port, PoolVector<String> p_protocols, bool gd_mp_api) { + + ERR_FAIL_COND_V(context != NULL, FAILED); + + _is_multiplayer = gd_mp_api; + + struct lws_context_creation_info info; + memset(&info, 0, sizeof info); + + if (p_protocols.size() == 0) // default to binary protocol + p_protocols.append(String("binary")); + + // Prepare lws protocol structs + _lws_make_protocols(this, &LWSServer::_lws_gd_callback, p_protocols, &_lws_ref); + + info.port = p_port; + info.user = _lws_ref; + info.protocols = _lws_ref->lws_structs; + info.gid = -1; + info.uid = -1; + //info.ws_ping_pong_interval = 5; + + context = lws_create_context(&info); + + if (context == NULL) { + _lws_free_ref(_lws_ref); + _lws_ref = NULL; + ERR_EXPLAIN("Unable to create LWS context"); + ERR_FAIL_V(FAILED); + } + + return OK; +} + +bool LWSServer::is_listening() const { + return context != NULL; +} + +int LWSServer::_handle_cb(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { + + LWSPeer::PeerData *peer_data = (LWSPeer::PeerData *)user; + + switch (reason) { + case LWS_CALLBACK_HTTP: + // no http for now + // closing immediately returning -1; + return -1; + + case LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION: + // check header here? + break; + + case LWS_CALLBACK_ESTABLISHED: { + int32_t id = _gen_unique_id(); + + Ref<LWSPeer> peer = Ref<LWSPeer>(memnew(LWSPeer)); + peer->set_wsi(wsi); + _peer_map[id] = peer; + + peer_data->peer_id = id; + peer_data->in_size = 0; + peer_data->in_count = 0; + peer_data->out_count = 0; + peer_data->rbw.resize(16); + peer_data->rbr.resize(16); + peer_data->force_close = false; + + _on_connect(id, lws_get_protocol(wsi)->name); + break; + } + + case LWS_CALLBACK_CLOSED: { + if (peer_data == NULL) + return 0; + int32_t id = peer_data->peer_id; + if (_peer_map.has(id)) { + _peer_map[id]->close(); + _peer_map.erase(id); + } + peer_data->in_count = 0; + peer_data->out_count = 0; + peer_data->rbr.resize(0); + peer_data->rbw.resize(0); + _on_disconnect(id); + return 0; // we can end here + } + + case LWS_CALLBACK_RECEIVE: { + int32_t id = peer_data->peer_id; + if (_peer_map.has(id)) { + static_cast<Ref<LWSPeer> >(_peer_map[id])->read_wsi(in, len); + if (_peer_map[id]->get_available_packet_count() > 0) + _on_peer_packet(id); + } + break; + } + + case LWS_CALLBACK_SERVER_WRITEABLE: { + if (peer_data->force_close) + return -1; + + int id = peer_data->peer_id; + if (_peer_map.has(id)) + static_cast<Ref<LWSPeer> >(_peer_map[id])->write_wsi(); + break; + } + + default: + break; + } + + return 0; +} + +void LWSServer::stop() { + if (context == NULL) + return; + + _peer_map.clear(); + destroy_context(); + context = NULL; +} + +bool LWSServer::has_peer(int p_id) const { + return _peer_map.has(p_id); +} + +Ref<WebSocketPeer> LWSServer::get_peer(int p_id) const { + ERR_FAIL_COND_V(!has_peer(p_id), NULL); + return _peer_map[p_id]; +} + +LWSServer::LWSServer() { + context = NULL; + _lws_ref = NULL; +} + +LWSServer::~LWSServer() { + invalidate_lws_ref(); // we do not want any more callbacks + stop(); +} + +#endif // JAVASCRIPT_ENABLED diff --git a/modules/websocket/lws_server.h b/modules/websocket/lws_server.h new file mode 100644 index 0000000000..5f7ac4850a --- /dev/null +++ b/modules/websocket/lws_server.h @@ -0,0 +1,63 @@ +/*************************************************************************/ +/* lws_server.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#ifndef LWSSERVER_H +#define LWSSERVER_H + +#ifndef JAVASCRIPT_ENABLED + +#include "core/reference.h" +#include "lws_helper.h" +#include "lws_peer.h" +#include "websocket_server.h" + +class LWSServer : public WebSocketServer { + + GDCIIMPL(LWSServer, WebSocketServer); + + LWS_HELPER(LWSServer); + +private: + Map<int, Ref<LWSPeer> > peer_map; + +public: + Error listen(int p_port, PoolVector<String> p_protocols = PoolVector<String>(), bool gd_mp_api = false); + void stop(); + bool is_listening() const; + bool has_peer(int p_id) const; + Ref<WebSocketPeer> get_peer(int p_id) const; + virtual void poll() { _lws_poll(); } + + LWSServer(); + ~LWSServer(); +}; + +#endif // JAVASCRIPT_ENABLED + +#endif // LWSSERVER_H diff --git a/modules/websocket/register_types.cpp b/modules/websocket/register_types.cpp new file mode 100644 index 0000000000..39d03ff1f0 --- /dev/null +++ b/modules/websocket/register_types.cpp @@ -0,0 +1,79 @@ +/*************************************************************************/ +/* register_types.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "register_types.h" +#include "error_macros.h" +#ifdef JAVASCRIPT_ENABLED +#include "emscripten.h" +#include "emws_client.h" +#include "emws_peer.h" +#include "emws_server.h" +#else +#include "lws_client.h" +#include "lws_peer.h" +#include "lws_server.h" +#endif + +void register_websocket_types() { +#ifdef JAVASCRIPT_ENABLED + EM_ASM({ + var IDHandler = {}; + IDHandler["ids"] = {}; + IDHandler["has"] = function(id) { + return IDHandler.ids.hasOwnProperty(id); + }; + IDHandler["add"] = function(obj) { + var id = crypto.getRandomValues(new Int32Array(32))[0]; + IDHandler.ids[id] = obj; + return id; + }; + IDHandler["get"] = function(id) { + return IDHandler.ids[id]; + }; + IDHandler["remove"] = function(id) { + delete IDHandler.ids[id]; + }; + Module["IDHandler"] = IDHandler; + }); + EMWSPeer::make_default(); + EMWSClient::make_default(); + EMWSServer::make_default(); +#else + LWSPeer::make_default(); + LWSClient::make_default(); + LWSServer::make_default(); +#endif + + ClassDB::register_virtual_class<WebSocketMultiplayerPeer>(); + ClassDB::register_custom_instance_class<WebSocketServer>(); + ClassDB::register_custom_instance_class<WebSocketClient>(); + ClassDB::register_custom_instance_class<WebSocketPeer>(); +} + +void unregister_websocket_types() {} diff --git a/modules/websocket/register_types.h b/modules/websocket/register_types.h new file mode 100644 index 0000000000..010d88789b --- /dev/null +++ b/modules/websocket/register_types.h @@ -0,0 +1,31 @@ +/*************************************************************************/ +/* register_types.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +void register_websocket_types(); +void unregister_websocket_types(); diff --git a/modules/websocket/websocket_client.cpp b/modules/websocket/websocket_client.cpp new file mode 100644 index 0000000000..f92a386988 --- /dev/null +++ b/modules/websocket/websocket_client.cpp @@ -0,0 +1,124 @@ +/*************************************************************************/ +/* websocket_client.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "websocket_client.h" + +GDCINULL(WebSocketClient); + +WebSocketClient::WebSocketClient() { +} + +WebSocketClient::~WebSocketClient() { +} + +Error WebSocketClient::connect_to_url(String p_url, PoolVector<String> p_protocols, bool gd_mp_api) { + _is_multiplayer = gd_mp_api; + + String host = p_url; + String path = "/"; + int p_len = -1; + int port = 80; + bool ssl = false; + if (host.begins_with("wss://")) { + ssl = true; // we should implement this + host = host.substr(6, host.length() - 6); + port = 443; + } else { + ssl = false; + if (host.begins_with("ws://")) + host = host.substr(5, host.length() - 5); + } + + // Path + p_len = host.find("/"); + if (p_len != -1) { + path = host.substr(p_len, host.length() - p_len); + host = host.substr(0, p_len); + } + + // Port + p_len = host.find_last(":"); + if (p_len != -1 && p_len == host.find(":")) { + port = host.substr(p_len, host.length() - p_len).to_int(); + host = host.substr(0, p_len); + } + + return connect_to_host(host, path, port, ssl, p_protocols); +} + +bool WebSocketClient::is_server() const { + + return false; +} + +void WebSocketClient::_on_peer_packet() { + + if (_is_multiplayer) { + _process_multiplayer(get_peer(1), 1); + } else { + emit_signal("data_received"); + } +} + +void WebSocketClient::_on_connect(String p_protocol) { + + if (_is_multiplayer) { + // need to wait for ID confirmation... + } else { + emit_signal("connection_established", p_protocol); + } +} + +void WebSocketClient::_on_disconnect() { + + if (_is_multiplayer) { + emit_signal("connection_failed"); + } else { + emit_signal("connection_closed"); + } +} + +void WebSocketClient::_on_error() { + + if (_is_multiplayer) { + emit_signal("connection_failed"); + } else { + emit_signal("connection_error"); + } +} + +void WebSocketClient::_bind_methods() { + ClassDB::bind_method(D_METHOD("connect_to_url", "url", "protocols", "gd_mp_api"), &WebSocketClient::connect_to_url, DEFVAL(PoolVector<String>()), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("disconnect_from_host"), &WebSocketClient::disconnect_from_host); + + ADD_SIGNAL(MethodInfo("data_received")); + ADD_SIGNAL(MethodInfo("connection_established", PropertyInfo(Variant::STRING, "protocol"))); + ADD_SIGNAL(MethodInfo("connection_closed")); + ADD_SIGNAL(MethodInfo("connection_error")); +} diff --git a/modules/websocket/websocket_client.h b/modules/websocket/websocket_client.h new file mode 100644 index 0000000000..0e87825222 --- /dev/null +++ b/modules/websocket/websocket_client.h @@ -0,0 +1,68 @@ +/*************************************************************************/ +/* websocket_client.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#ifndef WEBSOCKET_CLIENT_H +#define WEBSOCKET_CLIENT_H + +#include "core/error_list.h" +#include "websocket_multiplayer.h" +#include "websocket_peer.h" + +class WebSocketClient : public WebSocketMultiplayerPeer { + + GDCLASS(WebSocketClient, WebSocketMultiplayerPeer); + GDCICLASS(WebSocketClient); + +protected: + Ref<WebSocketPeer> _peer; + + static void _bind_methods(); + +public: + Error connect_to_url(String p_url, PoolVector<String> p_protocols = PoolVector<String>(), bool gd_mp_api = false); + + virtual void poll() = 0; + virtual Error connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, PoolVector<String> p_protocol = PoolVector<String>()) = 0; + virtual void disconnect_from_host() = 0; + virtual IP_Address get_connected_host() const = 0; + virtual uint16_t get_connected_port() const = 0; + + virtual bool is_server() const; + virtual ConnectionStatus get_connection_status() const = 0; + + void _on_peer_packet(); + void _on_connect(String p_protocol); + void _on_disconnect(); + void _on_error(); + + WebSocketClient(); + ~WebSocketClient(); +}; + +#endif // WEBSOCKET_CLIENT_H diff --git a/modules/websocket/websocket_macros.h b/modules/websocket/websocket_macros.h new file mode 100644 index 0000000000..b5c2159806 --- /dev/null +++ b/modules/websocket/websocket_macros.h @@ -0,0 +1,63 @@ +/*************************************************************************/ +/* websocket_macros.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#ifndef WEBSOCKETMACTOS_H +#define WEBSOCKETMACTOS_H + +/* clang-format off */ +#define GDCICLASS(CNAME) \ +public:\ + static CNAME *(*_create)();\ +\ + static Ref<CNAME > create_ref() {\ +\ + if (!_create)\ + return Ref<CNAME >();\ + return Ref<CNAME >(_create());\ + }\ +\ + static CNAME *create() {\ +\ + if (!_create)\ + return NULL;\ + return _create();\ + }\ +protected:\ + +#define GDCINULL(CNAME) \ +CNAME *(*CNAME::_create)() = NULL; + +#define GDCIIMPL(IMPNAME, CNAME) \ +public:\ + static CNAME *_create() { return memnew(IMPNAME); }\ + static void make_default() { CNAME::_create = IMPNAME::_create; }\ +protected:\ +/* clang-format on */ + +#endif // WEBSOCKETMACTOS_H diff --git a/modules/websocket/websocket_multiplayer.cpp b/modules/websocket/websocket_multiplayer.cpp new file mode 100644 index 0000000000..8cd4dff38b --- /dev/null +++ b/modules/websocket/websocket_multiplayer.cpp @@ -0,0 +1,361 @@ +/*************************************************************************/ +/* websocket_multiplayer.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "websocket_multiplayer.h" +#include "core/os/os.h" + +WebSocketMultiplayerPeer::WebSocketMultiplayerPeer() { + + _is_multiplayer = false; + _peer_id = 0; + _target_peer = 0; + _refusing = false; + + _current_packet.source = 0; + _current_packet.destination = 0; + _current_packet.size = 0; + _current_packet.data = NULL; +} + +WebSocketMultiplayerPeer::~WebSocketMultiplayerPeer() { + + _clear(); +} + +int WebSocketMultiplayerPeer::_gen_unique_id() const { + + uint32_t hash = 0; + + while (hash == 0 || hash == 1) { + + hash = hash_djb2_one_32( + (uint32_t)OS::get_singleton()->get_ticks_usec()); + hash = hash_djb2_one_32( + (uint32_t)OS::get_singleton()->get_unix_time(), hash); + hash = hash_djb2_one_32( + (uint32_t)OS::get_singleton()->get_data_path().hash64(), hash); + hash = hash_djb2_one_32( + (uint32_t)((uint64_t)this), hash); //rely on aslr heap + hash = hash_djb2_one_32( + (uint32_t)((uint64_t)&hash), hash); //rely on aslr stack + hash = hash & 0x7FFFFFFF; // make it compatible with unsigned, since negatie id is used for exclusion + } + + return hash; +} +void WebSocketMultiplayerPeer::_clear() { + + _peer_map.clear(); + if (_current_packet.data != NULL) + memfree(_current_packet.data); + + for (List<Packet>::Element *E = _incoming_packets.front(); E; E = E->next()) { + memfree(E->get().data); + E->get().data = NULL; + } + + _incoming_packets.clear(); +} + +void WebSocketMultiplayerPeer::_bind_methods() { + + ClassDB::bind_method(D_METHOD("get_peer", "peer_id"), &WebSocketMultiplayerPeer::get_peer); + + ADD_SIGNAL(MethodInfo("peer_packet", PropertyInfo(Variant::INT, "peer_source"))); +} + +// +// PacketPeer +// +int WebSocketMultiplayerPeer::get_available_packet_count() const { + + ERR_FAIL_COND_V(!_is_multiplayer, ERR_UNCONFIGURED); + + return _incoming_packets.size(); +} + +int WebSocketMultiplayerPeer::get_max_packet_size() const { + + ERR_FAIL_COND_V(!_is_multiplayer, ERR_UNCONFIGURED); + + return MAX_PACKET_SIZE; +} + +Error WebSocketMultiplayerPeer::get_packet(const uint8_t **r_buffer, int &r_buffer_size) { + + r_buffer_size = 0; + ERR_FAIL_COND_V(!_is_multiplayer, ERR_UNCONFIGURED); + + if (_current_packet.data != NULL) { + memfree(_current_packet.data); + _current_packet.data = NULL; + } + + _current_packet = _incoming_packets.front()->get(); + _incoming_packets.pop_front(); + + *r_buffer = _current_packet.data; + r_buffer_size = _current_packet.size; + + return OK; +} + +Error WebSocketMultiplayerPeer::put_packet(const uint8_t *p_buffer, int p_buffer_size) { + + ERR_FAIL_COND_V(!_is_multiplayer, ERR_UNCONFIGURED); + + PoolVector<uint8_t> buffer = _make_pkt(SYS_NONE, get_unique_id(), _target_peer, p_buffer, p_buffer_size); + + if (is_server()) { + return _server_relay(1, _target_peer, &(buffer.read()[0]), buffer.size()); + } else { + return get_peer(1)->put_packet(&(buffer.read()[0]), buffer.size()); + } +} + +// +// NetworkedMultiplayerPeer +// +void WebSocketMultiplayerPeer::set_transfer_mode(TransferMode p_mode) { + + // Websocket uses TCP, reliable +} + +NetworkedMultiplayerPeer::TransferMode WebSocketMultiplayerPeer::get_transfer_mode() const { + + // Websocket uses TCP, reliable + return TRANSFER_MODE_RELIABLE; +} + +void WebSocketMultiplayerPeer::set_target_peer(int p_target_peer) { + + _target_peer = p_target_peer; +} + +int WebSocketMultiplayerPeer::get_packet_peer() const { + + ERR_FAIL_COND_V(!_is_multiplayer, 1); + ERR_FAIL_COND_V(_incoming_packets.size() == 0, 1); + + return _incoming_packets.front()->get().source; +} + +int WebSocketMultiplayerPeer::get_unique_id() const { + + return _peer_id; +} + +void WebSocketMultiplayerPeer::set_refuse_new_connections(bool p_enable) { + + _refusing = p_enable; +} + +bool WebSocketMultiplayerPeer::is_refusing_new_connections() const { + + return _refusing; +} + +void WebSocketMultiplayerPeer::_send_sys(Ref<WebSocketPeer> p_peer, uint8_t p_type, int32_t p_peer_id) { + + ERR_FAIL_COND(!p_peer.is_valid()); + ERR_FAIL_COND(!p_peer->is_connected_to_host()); + + PoolVector<uint8_t> message = _make_pkt(p_type, 1, 0, (uint8_t *)&p_peer_id, 4); + p_peer->put_packet(&(message.read()[0]), message.size()); +} + +PoolVector<uint8_t> WebSocketMultiplayerPeer::_make_pkt(uint32_t p_type, int32_t p_from, int32_t p_to, const uint8_t *p_data, uint32_t p_data_size) { + + PoolVector<uint8_t> out; + out.resize(PROTO_SIZE + p_data_size); + + PoolVector<uint8_t>::Write w = out.write(); + copymem(&w[0], &p_type, 1); + copymem(&w[1], &p_from, 4); + copymem(&w[5], &p_to, 4); + copymem(&w[PROTO_SIZE], p_data, p_data_size); + + return out; +} + +void WebSocketMultiplayerPeer::_send_add(int32_t p_peer_id) { + + // First of all, confirm the ID! + _send_sys(get_peer(p_peer_id), SYS_ID, p_peer_id); + + // Then send the server peer (which will trigger connection_succeded in client) + _send_sys(get_peer(p_peer_id), SYS_ADD, 1); + + for (Map<int, Ref<WebSocketPeer> >::Element *E = _peer_map.front(); E; E = E->next()) { + uint32_t id = E->key(); + if (p_peer_id == id) + continue; // Skip the newwly added peer (already confirmed) + + // Send new peer to others + _send_sys(get_peer(id), SYS_ADD, p_peer_id); + // Send others to new peer + _send_sys(get_peer(p_peer_id), SYS_ADD, id); + } +} + +void WebSocketMultiplayerPeer::_send_del(int32_t p_peer_id) { + for (Map<int, Ref<WebSocketPeer> >::Element *E = _peer_map.front(); E; E = E->next()) { + uint32_t id = E->key(); + if (p_peer_id != id) + _send_sys(get_peer(id), SYS_DEL, p_peer_id); + } +} + +void WebSocketMultiplayerPeer::_store_pkt(int32_t p_source, int32_t p_dest, const uint8_t *p_data, uint32_t p_data_size) { + Packet packet; + packet.data = (uint8_t *)memalloc(p_data_size); + packet.size = p_data_size; + packet.source = p_source; + packet.destination = p_dest; + copymem(packet.data, &p_data[PROTO_SIZE], p_data_size); + _incoming_packets.push_back(packet); + emit_signal("peer_packet", p_source); +} + +Error WebSocketMultiplayerPeer::_server_relay(int32_t p_from, int32_t p_to, const uint8_t *p_buffer, uint32_t p_buffer_size) { + if (p_to == 1) { + + return OK; // Will not send to self + + } else if (p_to == 0) { + + for (Map<int, Ref<WebSocketPeer> >::Element *E = _peer_map.front(); E; E = E->next()) { + if (E->key() != p_from) + E->get()->put_packet(p_buffer, p_buffer_size); + } + return OK; // Sent to all but sender + + } else if (p_to < 0) { + + for (Map<int, Ref<WebSocketPeer> >::Element *E = _peer_map.front(); E; E = E->next()) { + if (E->key() != p_from && E->key() != -p_to) + E->get()->put_packet(p_buffer, p_buffer_size); + } + return OK; // Sent to all but sender and excluded + + } else { + + ERR_FAIL_COND_V(p_to == p_from, FAILED); + + return get_peer(p_to)->put_packet(p_buffer, p_buffer_size); // Sending to specific peer + } +} + +void WebSocketMultiplayerPeer::_process_multiplayer(Ref<WebSocketPeer> p_peer, uint32_t p_peer_id) { + + ERR_FAIL_COND(!p_peer.is_valid()); + + const uint8_t *in_buffer; + int size = 0; + int data_size = 0; + + Error err = p_peer->get_packet(&in_buffer, size); + + ERR_FAIL_COND(err != OK); + ERR_FAIL_COND(size < PROTO_SIZE); + + data_size = size - PROTO_SIZE; + + uint8_t type = 0; + int32_t from = 0; + int32_t to = 0; + copymem(&type, in_buffer, 1); + copymem(&from, &in_buffer[1], 4); + copymem(&to, &in_buffer[5], 4); + + if (is_server()) { // Server can resend + + ERR_FAIL_COND(type != SYS_NONE); // Only server sends sys messages + ERR_FAIL_COND(from != p_peer_id); // Someone is cheating + + _server_relay(from, to, in_buffer, size); // Relay if needed + + if (to == 1) { // This is for the server + + _store_pkt(from, to, in_buffer, data_size); + + } else if (to == 0) { + + // Broadcast, for us too + _store_pkt(from, to, in_buffer, data_size); + + } else if (to < 0) { + + // All but one, for us if not excluded + if (_peer_id != -p_peer_id) + _store_pkt(from, to, in_buffer, data_size); + + } else { + + // Send to specific peer + ERR_FAIL_COND(!_peer_map.has(to)); + get_peer(to)->put_packet(in_buffer, size); + } + + } else { + + if (type == SYS_NONE) { // Payload message + + _store_pkt(from, to, in_buffer, data_size); + return; + } + + // System message + ERR_FAIL_COND(data_size < 4); + int id = 0; + copymem(&id, &in_buffer[PROTO_SIZE], 4); + + switch (type) { + + case SYS_ADD: // Add peer + _peer_map[id] = Ref<WebSocketPeer>(); + emit_signal("peer_connected", id); + if (id == 1) // We just connected to the server + emit_signal("connection_succeeded"); + break; + + case SYS_DEL: // Remove peer + _peer_map.erase(id); + emit_signal("peer_disconnected", id); + break; + case SYS_ID: // Helo, server assigned ID + _peer_id = id; + break; + default: + ERR_EXPLAIN("Invalid multiplayer message"); + ERR_FAIL(); + break; + } + } +} diff --git a/modules/websocket/websocket_multiplayer.h b/modules/websocket/websocket_multiplayer.h new file mode 100644 index 0000000000..e8e795e97f --- /dev/null +++ b/modules/websocket/websocket_multiplayer.h @@ -0,0 +1,110 @@ +/*************************************************************************/ +/* websocket_multiplayer.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#ifndef WEBSOCKET_MULTIPLAYER_PEER_H +#define WEBSOCKET_MULTIPLAYER_PEER_H + +#include "core/error_list.h" +#include "core/io/networked_multiplayer_peer.h" +#include "core/list.h" +#include "websocket_peer.h" + +class WebSocketMultiplayerPeer : public NetworkedMultiplayerPeer { + + GDCLASS(WebSocketMultiplayerPeer, NetworkedMultiplayerPeer); + +private: + PoolVector<uint8_t> _make_pkt(uint32_t p_type, int32_t p_from, int32_t p_to, const uint8_t *p_data, uint32_t p_data_size); + void _store_pkt(int32_t p_source, int32_t p_dest, const uint8_t *p_data, uint32_t p_data_size); + Error _server_relay(int32_t p_from, int32_t p_to, const uint8_t *p_buffer, uint32_t p_buffer_size); + +protected: + enum { + SYS_NONE = 0, + SYS_ADD = 1, + SYS_DEL = 2, + SYS_ID = 3, + + PROTO_SIZE = 9, + SYS_PACKET_SIZE = 13, + MAX_PACKET_SIZE = 65536 - 14 // 5 websocket, 9 multiplayer + }; + + struct Packet { + int source; + int destination; + uint8_t *data; + uint32_t size; + }; + + List<Packet> _incoming_packets; + Map<int, Ref<WebSocketPeer> > _peer_map; + Packet _current_packet; + + bool _is_multiplayer; + int _target_peer; + int _peer_id; + int _refusing; + + static void _bind_methods(); + + void _send_add(int32_t p_peer_id); + void _send_sys(Ref<WebSocketPeer> p_peer, uint8_t p_type, int32_t p_peer_id); + void _send_del(int32_t p_peer_id); + int _gen_unique_id() const; + +public: + /* NetworkedMultiplayerPeer */ + void set_transfer_mode(TransferMode p_mode); + TransferMode get_transfer_mode() const; + void set_target_peer(int p_peer_id); + int get_packet_peer() const; + int get_unique_id() const; + virtual bool is_server() const = 0; + void set_refuse_new_connections(bool p_enable); + bool is_refusing_new_connections() const; + virtual ConnectionStatus get_connection_status() const = 0; + + /* PacketPeer */ + virtual int get_available_packet_count() const; + virtual int get_max_packet_size() const; + virtual Error get_packet(const uint8_t **r_buffer, int &r_buffer_size); + virtual Error put_packet(const uint8_t *p_buffer, int p_buffer_size); + + /* WebSocketPeer */ + virtual Ref<WebSocketPeer> get_peer(int p_peer_id) const = 0; + + void _process_multiplayer(Ref<WebSocketPeer> p_peer, uint32_t p_peer_id); + void _clear(); + + WebSocketMultiplayerPeer(); + ~WebSocketMultiplayerPeer(); +}; + +#endif // WEBSOCKET_MULTIPLAYER_PEER_H diff --git a/modules/websocket/websocket_peer.cpp b/modules/websocket/websocket_peer.cpp new file mode 100644 index 0000000000..a6fbb4481b --- /dev/null +++ b/modules/websocket/websocket_peer.cpp @@ -0,0 +1,49 @@ +/*************************************************************************/ +/* websocket_peer.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "websocket_peer.h" + +GDCINULL(WebSocketPeer); + +WebSocketPeer::WebSocketPeer() { +} + +WebSocketPeer::~WebSocketPeer() { +} + +void WebSocketPeer::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_write_mode"), &WebSocketPeer::get_write_mode); + ClassDB::bind_method(D_METHOD("set_write_mode", "mode"), &WebSocketPeer::set_write_mode); + ClassDB::bind_method(D_METHOD("is_connected_to_host"), &WebSocketPeer::is_connected_to_host); + ClassDB::bind_method(D_METHOD("was_string_packet"), &WebSocketPeer::was_string_packet); + ClassDB::bind_method(D_METHOD("close"), &WebSocketPeer::close); + + BIND_ENUM_CONSTANT(WRITE_MODE_TEXT); + BIND_ENUM_CONSTANT(WRITE_MODE_BINARY); +} diff --git a/modules/websocket/websocket_peer.h b/modules/websocket/websocket_peer.h new file mode 100644 index 0000000000..f4d8ce3e38 --- /dev/null +++ b/modules/websocket/websocket_peer.h @@ -0,0 +1,73 @@ +/*************************************************************************/ +/* websocket_peer.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#ifndef WEBSOCKETPEER_H +#define WEBSOCKETPEER_H + +#include "core/error_list.h" +#include "core/io/packet_peer.h" +#include "core/ring_buffer.h" +#include "websocket_macros.h" + +class WebSocketPeer : public PacketPeer { + + GDCLASS(WebSocketPeer, PacketPeer); + GDCICLASS(WebSocketPeer); + +public: + enum WriteMode { + WRITE_MODE_TEXT, + WRITE_MODE_BINARY, + }; + +protected: + static void _bind_methods(); + +public: + virtual int get_available_packet_count() const = 0; + virtual Error get_packet(const uint8_t **r_buffer, int &r_buffer_size) = 0; + virtual Error put_packet(const uint8_t *p_buffer, int p_buffer_size) = 0; + virtual int get_max_packet_size() const = 0; + + virtual WriteMode get_write_mode() const = 0; + virtual void set_write_mode(WriteMode p_mode) = 0; + + virtual void close() = 0; + + virtual bool is_connected_to_host() const = 0; + virtual IP_Address get_connected_host() const = 0; + virtual uint16_t get_connected_port() const = 0; + virtual bool was_string_packet() const = 0; + + WebSocketPeer(); + ~WebSocketPeer(); +}; + +VARIANT_ENUM_CAST(WebSocketPeer::WriteMode); +#endif // WEBSOCKETPEER_H diff --git a/modules/websocket/websocket_server.cpp b/modules/websocket/websocket_server.cpp new file mode 100644 index 0000000000..ba77019f55 --- /dev/null +++ b/modules/websocket/websocket_server.cpp @@ -0,0 +1,94 @@ +/*************************************************************************/ +/* websocket_server.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "websocket_server.h" + +GDCINULL(WebSocketServer); + +WebSocketServer::WebSocketServer() { + _peer_id = 1; +} + +WebSocketServer::~WebSocketServer() { +} + +void WebSocketServer::_bind_methods() { + + ClassDB::bind_method(D_METHOD("is_listening"), &WebSocketServer::is_listening); + ClassDB::bind_method(D_METHOD("listen", "port", "protocols", "gd_mp_api"), &WebSocketServer::listen, DEFVAL(PoolVector<String>()), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("stop"), &WebSocketServer::stop); + ClassDB::bind_method(D_METHOD("has_peer", "id"), &WebSocketServer::has_peer); + + ADD_SIGNAL(MethodInfo("client_disconnected", PropertyInfo(Variant::INT, "id"))); + ADD_SIGNAL(MethodInfo("client_connected", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::STRING, "protocol"))); + ADD_SIGNAL(MethodInfo("data_received", PropertyInfo(Variant::INT, "id"))); +} + +NetworkedMultiplayerPeer::ConnectionStatus WebSocketServer::get_connection_status() const { + if (is_listening()) + return CONNECTION_CONNECTED; + + return CONNECTION_DISCONNECTED; +}; + +bool WebSocketServer::is_server() const { + + return true; +} + +void WebSocketServer::_on_peer_packet(int32_t p_peer_id) { + + if (_is_multiplayer) { + _process_multiplayer(get_peer(p_peer_id), p_peer_id); + } else { + emit_signal("data_received", p_peer_id); + } +} + +void WebSocketServer::_on_connect(int32_t p_peer_id, String p_protocol) { + + if (_is_multiplayer) { + // Send add to clients + _send_add(p_peer_id); + emit_signal("peer_connected", p_peer_id); + } else { + emit_signal("client_connected", p_peer_id, p_protocol); + } +} + +void WebSocketServer::_on_disconnect(int32_t p_peer_id) { + + if (_is_multiplayer) { + // Send delete to clients + _send_del(p_peer_id); + emit_signal("peer_disconnected", p_peer_id); + } else { + emit_signal("client_disconnected", p_peer_id); + } +} diff --git a/modules/websocket/websocket_server.h b/modules/websocket/websocket_server.h new file mode 100644 index 0000000000..db188811fd --- /dev/null +++ b/modules/websocket/websocket_server.h @@ -0,0 +1,63 @@ +/*************************************************************************/ +/* websocket_server.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#ifndef WEBSOCKET_H +#define WEBSOCKET_H + +#include "core/reference.h" +#include "websocket_multiplayer.h" +#include "websocket_peer.h" + +class WebSocketServer : public WebSocketMultiplayerPeer { + + GDCLASS(WebSocketServer, WebSocketMultiplayerPeer); + GDCICLASS(WebSocketServer); + +protected: + static void _bind_methods(); + +public: + virtual void poll() = 0; + virtual Error listen(int p_port, PoolVector<String> p_protocols = PoolVector<String>(), bool gd_mp_api = false) = 0; + virtual void stop() = 0; + virtual bool is_listening() const = 0; + virtual bool has_peer(int p_id) const = 0; + virtual Ref<WebSocketPeer> get_peer(int p_id) const = 0; + virtual bool is_server() const; + ConnectionStatus get_connection_status() const; + + void _on_peer_packet(int32_t p_peer_id); + void _on_connect(int32_t p_peer_id, String p_protocol); + void _on_disconnect(int32_t p_peer_id); + + WebSocketServer(); + ~WebSocketServer(); +}; + +#endif // WEBSOCKET_H |