Bug 214910

Summary: Refactor HID gamepad code to be much less fragile and much easier to hack on
Product: WebKit Reporter: Brady Eidson <beidson>
Component: WebCore Misc.Assignee: Brady Eidson <beidson>
Status: RESOLVED FIXED    
Severity: Normal CC: darin, sam, thorton, webkit-bug-importer
Priority: P2 Keywords: InRadar
Version: WebKit Nightly Build   
Hardware: Unspecified   
OS: Unspecified   
Bug Depends on:    
Bug Blocks: 214911    
Attachments:
Description Flags
Patch
none
Patch none

Description Brady Eidson 2020-07-28 22:21:56 PDT
Refactor HID gamepad code to be much less fragile and much easier to hack on

This will pave the way for further improvements, such as trivially mapping specific controllers
Comment 1 Brady Eidson 2020-07-28 22:22:36 PDT
(In reply to Brady Eidson from comment #0)
> Refactor HID gamepad code to be much less fragile and much easier to hack on
> 
> This will pave the way for further improvements, such as trivially mapping
> specific controllers

Such as over in https://bugs.webkit.org/show_bug.cgi?id=214911
Comment 2 Brady Eidson 2020-07-29 15:47:22 PDT
Created attachment 405521 [details]
Patch
Comment 3 Darin Adler 2020-07-29 16:56:51 PDT
Comment on attachment 405521 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=405521&action=review

> Source/WebCore/platform/gamepad/SharedGamepadValue.h:36
> +class SharedGamepadValue {

The design of this class is a bit tricky. If you assign one to another then it redirects which shared value is being used. But if you assign a double to it, the shared value is overwritten. Both are done with the same "=" operator. I think there’s a reason to consider pointer-style boxing instead to distinguish those types of assuming.

> Source/WebCore/platform/gamepad/SharedGamepadValue.h:37
> +    WTF_MAKE_FAST_ALLOCATED;

This is needed on SharedGamepadValue::Data, which is heap allocated, and probably is not needed on this class, which won’t be.

> Source/WebCore/platform/gamepad/SharedGamepadValue.h:63
> +        Data()
> +            : value(0.0)
> +        {
> +        }
> +

No need for this. Could just put a 0 up above in the default constructor.

> Source/WebCore/platform/gamepad/mac/GenericHIDGamepad.cpp:41
> +    for (auto element : inputElements) {

Should use auto& so we don’t copy all the elements while iterating.

> Source/WebCore/platform/gamepad/mac/GenericHIDGamepad.cpp:71
> +        m_axisValues.append(SharedGamepadValue { });

I think just { } will do it without a class name. Or maybe you’d prefer 0.0?

> Source/WebCore/platform/gamepad/mac/GenericHIDGamepad.h:33
> +#include <wtf/FastMalloc.h>
> +#include <wtf/HexNumber.h>
> +#include <wtf/NeverDestroyed.h>

These should not be needed in the header.

> Source/WebCore/platform/gamepad/mac/GenericHIDGamepad.h:37
> +class GenericHIDGamepad : public HIDGamepad {

Can this class be final?

> Source/WebCore/platform/gamepad/mac/GenericHIDGamepad.h:41
> +protected:

Why protected instead of private? Is this a base class?

> Source/WebCore/platform/gamepad/mac/HIDGamepadElement.h:54
> +    HIDGamepadElement(const HIDElement&);

What is this used for?

> Source/WebCore/platform/gamepad/mac/HIDGamepadElement.h:60
> +class HIDGamepadButton : public HIDGamepadElement {

final?

> Source/WebCore/platform/gamepad/mac/HIDGamepadElement.h:62
> +    HIDGamepadButton(const HIDElement& element, SharedGamepadValue value)

Taking a SharedGamepadValue is OK, but intrinsically churn-y, like taking a Ref<>, as opposed to either a const Ref<>& or a Ref<>&&.

> Source/WebCore/platform/gamepad/mac/HIDGamepadElement.h:64
> +        , m_value(value)

WTFMove here would save some reference count churn.

> Source/WebCore/platform/gamepad/mac/HIDGamepadElement.h:70
> +protected:

Why protected? Is this a base class?

> Source/WebCore/platform/gamepad/mac/HIDGamepadElement.h:71
> +    HIDInputType gamepadValueChanged(IOHIDValueRef) override;

final?

> Source/WebCore/platform/gamepad/mac/HIDGamepadElement.h:74
> +private:
> +    SharedGamepadValue m_value;

Could move this to be an HIDGamepadElement data member, either protected or private, since both derived classes have it.

> Source/WebCore/platform/gamepad/mac/HIDGamepadElement.h:77
> +class HIDGamepadAxis : public HIDGamepadElement {

final?

> Source/WebCore/platform/gamepad/mac/HIDGamepadElement.h:79
> +    HIDGamepadAxis(const HIDElement& element, SharedGamepadValue value)

Taking a SharedGamepadValue is OK, but intrinsically churn-y, like taking a Ref<>, as opposed to either a const Ref<>& or a Ref<>&&.

> Source/WebCore/platform/gamepad/mac/HIDGamepadElement.h:81
> +        , m_value(value)

WTFMove here would save some reference count churn.

> Source/WebCore/platform/gamepad/mac/HIDGamepadElement.h:88
> +protected:

Why protected? Is this a base class?

> Source/WebCore/platform/gamepad/mac/HIDGamepadElement.h:89
> +    HIDInputType gamepadValueChanged(IOHIDValueRef) override;

final?

> Source/WebCore/platform/gamepad/mac/HIDGamepadElement.h:92
> +private:
> +    SharedGamepadValue m_value;

Could move this to be an HIDGamepadElement data member, either protected or private, since both derived classes have it.

> Source/WebCore/platform/mac/HIDDevice.cpp:83
> +        if (encounteredCookies.contains(cookie))
> +            continue;

Can we do the add operation here along with the contains as a single operation to avoid double hashing, or would that be wasteful or incorrect for the same element types?

> Source/WebCore/platform/mac/HIDDevice.cpp:99
> +        if (type == kIOHIDElementTypeCollection) {
> +            auto children = IOHIDElementGetChildren(element);
> +            for (CFIndex i = 0; i < CFArrayGetCount(children); ++i)
> +                elementQueue.append(checked_cf_cast<IOHIDElementRef>(CFArrayGetValueAtIndex(children, i)));
> +            continue;
> +        }
> +
> +        switch (type) {
> +        case kIOHIDElementTypeCollection: {
> +            auto children = IOHIDElementGetChildren(element);
> +            for (CFIndex i = 0; i < CFArrayGetCount(children); ++i)
> +                elementQueue.append(checked_cf_cast<IOHIDElementRef>(CFArrayGetValueAtIndex(children, i)));
> +            continue;
> +        }

This code is repeated twice. I presume you want to delete the if statement version.

> Source/WebCore/platform/mac/HIDDevice.h:30
> +#include <IOKit/hid/IOHIDDevice.h>

Can we forward declare IOHIDDeviceRef instead of pulling in the header?

> Source/WebCore/platform/mac/HIDDevice.h:32
> +#include <wtf/Vector.h>

Forward declaration should suffice. No actual vectors in this header.

> Source/WebCore/platform/mac/HIDDevice.h:39
> +    WTF_MAKE_FAST_ALLOCATED;

This is OK regardless, but do we ever allocate these on the heap?

> Source/WebCore/platform/mac/HIDDevice.h:41
> +    HIDDevice(IOHIDDeviceRef);

I suggest using explicit here.

> Source/WebCore/platform/mac/HIDElement.h:38
> +    HIDElement(IOHIDElementRef);

explicit

> Source/WebCore/platform/mac/HIDElement.h:39
> +    HIDElement(const HIDElement&) = default;

Are you doing this instead of Noncopyable, to usher in a new age?

> Source/WebCore/platform/mac/HIDElement.h:40
> +    virtual ~HIDElement() { }

No other virtual functions except the destructor? This is an unusual design pattern. Maybe this base class is only to help build derived classes and not for polymorphism. If so, maybe the constructor should be protected?
Comment 4 Brady Eidson 2020-07-29 22:01:39 PDT
In typical "Darin mega-review" fashion, I took most advice.

Some direct replies inline.

(In reply to Darin Adler from comment #3)
> Comment on attachment 405521 [details]
> Patch
> 
> View in context:
> https://bugs.webkit.org/attachment.cgi?id=405521&action=review
> 
> > Source/WebCore/platform/gamepad/SharedGamepadValue.h:36
> > +class SharedGamepadValue {
> 
> The design of this class is a bit tricky. If you assign one to another then
> it redirects which shared value is being used. But if you assign a double to
> it, the shared value is overwritten. Both are done with the same "="
> operator. I think there’s a reason to consider pointer-style boxing instead
> to distinguish those types of assuming.

Removed the explicit double()ness and just added a setter and getter.

> 
> > Source/WebCore/platform/gamepad/mac/HIDGamepadElement.h:54
> > +    HIDGamepadElement(const HIDElement&);
> 
> What is this used for?

First a HIDDevice is made.

Then it creates an array of base-class HIDElements.

Then a GenericHIDGamepad is created with that HIDDevice.

It walks that array, then creates specific subclasses of them (HIDGamepadButton, HIDGamepadAxis)
Those subclasses pass the original HIDElement data back down through the constructor chain so it doesn't need to be re-fetched.

> 
> > Source/WebCore/platform/gamepad/mac/HIDGamepadElement.h:60
> > +class HIDGamepadButton : public HIDGamepadElement {
> 
> final?

In a future patch no! But for this patch, sure.

> 
> > Source/WebCore/platform/gamepad/mac/HIDGamepadElement.h:70
> > +protected:
> 
> Why protected? Is this a base class?

And

> > Source/WebCore/platform/gamepad/mac/HIDGamepadElement.h:88
> > +protected:
> 
> Why protected? Is this a base class?

And
 
> > Source/WebCore/platform/gamepad/mac/HIDGamepadElement.h:74
> > +private:
> > +    SharedGamepadValue m_value;
> 
> Could move this to be an HIDGamepadElement data member, either protected or
> private, since both derived classes have it.

This my (undocumented) future knowledge of where this is going in the next pass.
I can make some of these for now.

> > Source/WebCore/platform/gamepad/mac/HIDGamepadElement.h:92
> > +private:
> > +    SharedGamepadValue m_value;
> 
> Could move this to be an HIDGamepadElement data member, either protected or
> private, since both derived classes have it.

I'll do that for now (but it will change quickly thereafter)

> > Source/WebCore/platform/mac/HIDDevice.cpp:83
> > +        if (encounteredCookies.contains(cookie))
> > +            continue;
> 
> Can we do the add operation here along with the contains as a single
> operation to avoid double hashing, or would that be wasteful or incorrect
> for the same element types?

It wouldn't be correct for some types.

> > Source/WebCore/platform/mac/HIDElement.h:39
> > +    HIDElement(const HIDElement&) = default;
> 
> Are you doing this instead of Noncopyable, to usher in a new age?

You might've misread `default` as `delete`

In an earlier version I needed the explicit default here, but no longer needed!

> 
> > Source/WebCore/platform/mac/HIDElement.h:40
> > +    virtual ~HIDElement() { }
> 
> No other virtual functions except the destructor? This is an unusual design
> pattern. Maybe this base class is only to help build derived classes and not
> for polymorphism. If so, maybe the constructor should be protected?

Earlier version needed it here, but now the virtual d'tor up in HIDGamepadElement is good enough, so I removed this.
Comment 5 Brady Eidson 2020-07-29 23:40:16 PDT
Created attachment 405552 [details]
Patch
Comment 6 EWS 2020-07-30 00:21:48 PDT
Committed r265079: <https://trac.webkit.org/changeset/265079>

All reviewed patches have been landed. Closing bug and clearing flags on attachment 405552 [details].
Comment 7 Radar WebKit Bug Importer 2020-07-30 00:22:20 PDT
<rdar://problem/66314709>