refactor(core): route every event through one dispatch path
The registry now implements EventSignature and Dispatch like any generated object, so its events use the same validation and ownership path. Removed the paths that never ran for registered objects: AddListener and notifyListeners, the EventDispatcher (RegisterEventHandler, BatchDispatch, RawEvent, PerformanceStats), and the unused lastError fields.
- commit
- b69dc789dffd2aec5137bc8dd11463d959fc395f
- author
- brice · · 2026-09-29 15:46 UTC
- committer
- brice · · 2026-09-29 15:46 UTC
- parent
- 82fabc3
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
5 files · +51 -438
M client.go +46 -154
| @@ -56,21 +56,11 @@ type Display struct { | |||
| 56 | 56 | recvMu sync.Mutex | |
| 57 | 57 | dispatchMu sync.Mutex // serialize readers without holding receive state across handlers | |
| 58 | 58 | signatures sync.Map | |
| 59 | - | listenerMu sync.Mutex | |
| 60 | - | listeners sync.Map // map[uint32]map[uint16][]func([]byte) | |
| 61 | - | ||
| 62 | - | // High-performance event dispatcher | |
| 63 | - | dispatcher *EventDispatcher | |
| 64 | 59 | ||
| 65 | 60 | // Core objects | |
| 66 | 61 | registry *Registry | |
| 67 | 62 | context *Context // Store context once | |
| 68 | 63 | ||
| 69 | - | // Error state | |
| 70 | - | lastError error | |
| 71 | - | lastErrorCode uint32 | |
| 72 | - | lastErrorObj uint32 | |
| 73 | - | ||
| 74 | 64 | // Connection-local receive state. rbuf holds bytes read from the socket | |
| 75 | 65 | // that do not yet form a complete frame; recvBuf is the scratch buffer | |
| 76 | 66 | // used for a single read. pendingFDs holds ancillary descriptors that | |
| @@ -86,9 +76,8 @@ type Display struct { | |||
| 86 | 76 | // newDisplay builds a Display over an established connection. | |
| 87 | 77 | func newDisplay(conn net.Conn) *Display { | |
| 88 | 78 | d := &Display{ | |
| 89 | - | conn: conn, | |
| 90 | - | nextID: 2, // 1 is reserved for wl_display | |
| 91 | - | dispatcher: NewEventDispatcher(), | |
| 79 | + | conn: conn, | |
| 80 | + | nextID: 2, // 1 is reserved for wl_display | |
| 92 | 81 | } | |
| 93 | 82 | if uc, ok := conn.(*net.UnixConn); ok { | |
| 94 | 83 | d.unix = uc | |
| @@ -138,7 +127,6 @@ func (c *callbackObject) Dispatch(event *Event) { | |||
| 138 | 127 | if event.Opcode != 0 { // done event | |
| 139 | 128 | return | |
| 140 | 129 | } | |
| 141 | - | c.display.notifyListeners(c.id, 0, event.data) | |
| 142 | 130 | c.display.context.Unregister(c) | |
| 143 | 131 | } | |
| 144 | 132 | ||
| @@ -212,11 +200,6 @@ func (d *Display) ID() uint32 { | |||
| 212 | 200 | return 1 | |
| 213 | 201 | } | |
| 214 | 202 | ||
| 215 | - | // RegisterEventHandler registers a high-performance event handler | |
| 216 | - | func (d *Display) RegisterEventHandler(objectID uint32, opcode uint16, handler EventHandler) { | |
| 217 | - | d.dispatcher.RegisterHandler(objectID, opcode, handler) | |
| 218 | - | } | |
| 219 | - | ||
| 220 | 203 | // allocateID allocates a new object ID | |
| 221 | 204 | func (d *Display) allocateID() uint32 { | |
| 222 | 205 | d.idMu.Lock() | |
| @@ -481,17 +464,13 @@ type signatureProvider interface{ EventSignature(uint16) (string, bool) } | |||
| 481 | 464 | // dispatchFrame delivers one complete frame with precisely its signature's FDs. | |
| 482 | 465 | func (d *Display) dispatchFrame(f receivedFrame) error { | |
| 483 | 466 | d.recvMu.Lock() | |
| 484 | - | obj, ev, kind, err := d.prepareFrame(f) | |
| 467 | + | obj, ev, err := d.prepareFrame(f) | |
| 485 | 468 | d.recvMu.Unlock() | |
| 486 | 469 | if err != nil { | |
| 487 | 470 | return err | |
| 488 | 471 | } | |
| 489 | - | switch kind { | |
| 490 | - | case 1: | |
| 472 | + | if obj == nil { | |
| 491 | 473 | return d.handleDisplayEvent(f.opcode, f.body) | |
| 492 | - | case 2: | |
| 493 | - | d.notifyListeners(f.object, f.opcode, f.body) | |
| 494 | - | return nil | |
| 495 | 474 | } | |
| 496 | 475 | defer func() { | |
| 497 | 476 | for _, fd := range ev.fds { | |
| @@ -503,47 +482,31 @@ func (d *Display) dispatchFrame(f receivedFrame) error { | |||
| 503 | 482 | if _, isZombie := obj.(*zombie); isZombie { | |
| 504 | 483 | return nil | |
| 505 | 484 | } | |
| 506 | - | if proxy, ok := obj.(Proxy); ok { | |
| 507 | - | proxy.Dispatch(ev) | |
| 508 | - | } else { | |
| 509 | - | d.dispatcher.Dispatch(f.object, f.opcode, f.body) | |
| 510 | - | d.notifyListeners(f.object, f.opcode, f.body) | |
| 485 | + | if r, ok := obj.(eventReceiver); ok { | |
| 486 | + | r.Dispatch(ev) | |
| 511 | 487 | } | |
| 512 | 488 | return nil | |
| 513 | 489 | } | |
| 514 | 490 | ||
| 491 | + | // eventReceiver is any registered object that handles its own events: | |
| 492 | + | // generated proxies, callbacks and the registry. | |
| 493 | + | type eventReceiver interface{ Dispatch(*Event) } | |
| 494 | + | ||
| 515 | 495 | // prepareFrame assigns owned descriptors under recvMu, but never calls user code. | |
| 516 | - | func (d *Display) prepareFrame(f receivedFrame) (Object, *Event, uint8, error) { | |
| 496 | + | // A nil object with a nil error means the frame targets wl_display. | |
| 497 | + | func (d *Display) prepareFrame(f receivedFrame) (Object, *Event, error) { | |
| 517 | 498 | if d.closed.Load() { | |
| 518 | - | return nil, nil, 0, net.ErrClosed | |
| 499 | + | return nil, nil, net.ErrClosed | |
| 519 | 500 | } | |
| 520 | 501 | if f.object == displayObjectID { | |
| 521 | 502 | if len(d.pendingFDs) != 0 && len(d.rbuf) == 0 { | |
| 522 | - | return nil, nil, 0, &ProtocolError{Kind: "extra_fd", Err: ErrMalformedFrame} | |
| 523 | - | } | |
| 524 | - | return nil, nil, 1, nil | |
| 525 | - | } | |
| 526 | - | if f.object == d.registry.id { | |
| 527 | - | var sig string | |
| 528 | - | switch f.opcode { | |
| 529 | - | case 0: | |
| 530 | - | sig = "uint,string,uint," | |
| 531 | - | case 1: | |
| 532 | - | sig = "uint," | |
| 533 | - | default: | |
| 534 | - | return nil, nil, 0, &ProtocolError{Kind: "unknown_opcode", Object: f.object, Opcode: f.opcode, Err: ErrUnknownOpcode} | |
| 503 | + | return nil, nil, &ProtocolError{Kind: "extra_fd", Err: ErrMalformedFrame} | |
| 535 | 504 | } | |
| 536 | - | if err := validateEventBody(sig, f.body); err != nil { | |
| 537 | - | return nil, nil, 0, &ProtocolError{Kind: "malformed_payload", Object: f.object, Opcode: f.opcode, Err: err} | |
| 538 | - | } | |
| 539 | - | if len(d.pendingFDs) != 0 && len(d.rbuf) == 0 { | |
| 540 | - | return nil, nil, 0, &ProtocolError{Kind: "extra_fd", Object: f.object, Opcode: f.opcode, Err: ErrMalformedFrame} | |
| 541 | - | } | |
| 542 | - | return nil, nil, 2, nil | |
| 505 | + | return nil, nil, nil | |
| 543 | 506 | } | |
| 544 | 507 | obj, ok := d.objects.Load(f.object) | |
| 545 | 508 | if !ok { | |
| 546 | - | return nil, nil, 0, &ProtocolError{Kind: "unknown_object", Object: f.object, Opcode: f.opcode, Err: ErrUnknownObject} | |
| 509 | + | return nil, nil, &ProtocolError{Kind: "unknown_object", Object: f.object, Opcode: f.opcode, Err: ErrUnknownObject} | |
| 547 | 510 | } | |
| 548 | 511 | sigObj := obj | |
| 549 | 512 | z, isZombie := obj.(*zombie) | |
| @@ -562,14 +525,14 @@ func (d *Display) prepareFrame(f receivedFrame) (Object, *Event, uint8, error) { | |||
| 562 | 525 | } | |
| 563 | 526 | } | |
| 564 | 527 | if !valid { | |
| 565 | - | return nil, nil, 0, &ProtocolError{Kind: "unknown_opcode", Object: f.object, Opcode: f.opcode, Err: ErrUnknownOpcode} | |
| 528 | + | return nil, nil, &ProtocolError{Kind: "unknown_opcode", Object: f.object, Opcode: f.opcode, Err: ErrUnknownOpcode} | |
| 566 | 529 | } | |
| 567 | 530 | if err := validateEventBody(sig, f.body); err != nil { | |
| 568 | - | return nil, nil, 0, &ProtocolError{Kind: "malformed_payload", Object: f.object, Opcode: f.opcode, Err: err} | |
| 531 | + | return nil, nil, &ProtocolError{Kind: "malformed_payload", Object: f.object, Opcode: f.opcode, Err: err} | |
| 569 | 532 | } | |
| 570 | 533 | count := strings.Count(sig, "fd,") | |
| 571 | 534 | if len(d.pendingFDs) < count { | |
| 572 | - | return nil, nil, 0, &ProtocolError{Kind: "missing_fd", Object: f.object, Opcode: f.opcode, Err: ErrMalformedFrame} | |
| 535 | + | return nil, nil, &ProtocolError{Kind: "missing_fd", Object: f.object, Opcode: f.opcode, Err: ErrMalformedFrame} | |
| 573 | 536 | } | |
| 574 | 537 | ev := eventPool.Get().(*Event) | |
| 575 | 538 | *ev = Event{ProxyID: f.object, Opcode: f.opcode, data: f.body, display: d} | |
| @@ -588,11 +551,11 @@ func (d *Display) prepareFrame(f receivedFrame) (Object, *Event, uint8, error) { | |||
| 588 | 551 | } | |
| 589 | 552 | *ev = Event{} | |
| 590 | 553 | eventPool.Put(ev) | |
| 591 | - | return nil, nil, 0, &ProtocolError{Kind: "extra_fd", Object: f.object, Opcode: f.opcode, Err: ErrMalformedFrame} | |
| 554 | + | return nil, nil, &ProtocolError{Kind: "extra_fd", Object: f.object, Opcode: f.opcode, Err: ErrMalformedFrame} | |
| 592 | 555 | } | |
| 593 | 556 | // For a zombie the descriptors were still consumed above so the stream | |
| 594 | 557 | // stays aligned; dispatchFrame closes them without running any handler. | |
| 595 | - | return obj.(Object), ev, 0, nil | |
| 558 | + | return obj.(Object), ev, nil | |
| 596 | 559 | } | |
| 597 | 560 | ||
| 598 | 561 | type signatureKey struct { | |
| @@ -609,27 +572,6 @@ func (d *Display) RegisterEventSignature(object uint32, opcode uint16, signature | |||
| 609 | 572 | d.signatures.Store(signatureKey{object, opcode}, signature) | |
| 610 | 573 | } | |
| 611 | 574 | ||
| 612 | - | // notifyListeners runs the listeners registered for an object and opcode. | |
| 613 | - | func (d *Display) notifyListeners(objectID uint32, opcode uint16, body []byte) { | |
| 614 | - | listeners, ok := d.listeners.Load(objectID) | |
| 615 | - | if !ok { | |
| 616 | - | return | |
| 617 | - | } | |
| 618 | - | opcodeMap, ok := listeners.(*sync.Map) | |
| 619 | - | if !ok { | |
| 620 | - | return | |
| 621 | - | } | |
| 622 | - | handlers, ok := opcodeMap.Load(opcode) | |
| 623 | - | if !ok { | |
| 624 | - | return | |
| 625 | - | } | |
| 626 | - | for _, handler := range handlers.([]func([]byte)) { | |
| 627 | - | if handler != nil { | |
| 628 | - | handler(body) | |
| 629 | - | } | |
| 630 | - | } | |
| 631 | - | } | |
| 632 | - | ||
| 633 | 575 | // handleDisplayEvent handles events on the display object | |
| 634 | 576 | func (d *Display) handleDisplayEvent(opcode uint16, data []byte) error { | |
| 635 | 577 | switch opcode { | |
| @@ -640,12 +582,7 @@ func (d *Display) handleDisplayEvent(opcode uint16, data []byte) error { | |||
| 640 | 582 | event := &Event{ProxyID: displayObjectID, Opcode: opcode, data: data, display: d} | |
| 641 | 583 | objectID := event.Uint32() | |
| 642 | 584 | code := event.Uint32() | |
| 643 | - | message := event.String() | |
| 644 | - | ||
| 645 | - | d.lastErrorCode = code | |
| 646 | - | d.lastErrorObj = objectID | |
| 647 | - | d.lastError = &DisplayError{ObjectID: objectID, Code: code, Message: message} | |
| 648 | - | return d.lastError | |
| 585 | + | return &DisplayError{ObjectID: objectID, Code: code, Message: event.String()} | |
| 649 | 586 | ||
| 650 | 587 | case 1: // delete_id | |
| 651 | 588 | if len(data) != 4 { | |
| @@ -692,35 +629,9 @@ func (d *Display) Roundtrip() error { | |||
| 692 | 629 | } | |
| 693 | 630 | } | |
| 694 | 631 | ||
| 695 | - | // AddListener adds an event listener for an object | |
| 696 | - | func (d *Display) AddListener(objectID uint32, opcode uint16, handler func([]byte)) { | |
| 697 | - | if handler == nil { | |
| 698 | - | return | |
| 699 | - | } | |
| 700 | - | // Load or create listener map for object | |
| 701 | - | listeners, _ := d.listeners.LoadOrStore(objectID, &sync.Map{}) | |
| 702 | - | opcodeMap := listeners.(*sync.Map) | |
| 703 | - | ||
| 704 | - | d.listenerMu.Lock() | |
| 705 | - | defer d.listenerMu.Unlock() | |
| 706 | - | var handlers []func([]byte) | |
| 707 | - | if old, ok := opcodeMap.Load(opcode); ok { | |
| 708 | - | handlers = old.([]func([]byte)) | |
| 709 | - | } | |
| 710 | - | next := make([]func([]byte), len(handlers)+1) | |
| 711 | - | copy(next, handlers) | |
| 712 | - | next[len(handlers)] = handler | |
| 713 | - | opcodeMap.Store(opcode, next) | |
| 714 | - | } | |
| 715 | - | ||
| 716 | - | // getRegistry gets the global registry | |
| 632 | + | // getRegistry sends wl_display.get_registry for the bootstrap registry. | |
| 717 | 633 | func (d *Display) getRegistry() error { | |
| 718 | - | // Add registry listeners | |
| 719 | - | d.AddListener(d.registry.id, 0, d.registry.handleGlobal) | |
| 720 | - | d.AddListener(d.registry.id, 1, d.registry.handleGlobalRemove) | |
| 721 | - | ||
| 722 | - | // Send get_registry request (opcode 1) | |
| 723 | - | return d.SendRequest(1, 1, d.registry.id) | |
| 634 | + | return d.SendRequest(displayObjectID, 1, d.registry.id) | |
| 724 | 635 | } | |
| 725 | 636 | ||
| 726 | 637 | // Registry returns the global registry | |
| @@ -733,59 +644,40 @@ func (r *Registry) ID() uint32 { | |||
| 733 | 644 | return r.id | |
| 734 | 645 | } | |
| 735 | 646 | ||
| 736 | - | // handleGlobal handles global announcements | |
| 737 | - | func (r *Registry) handleGlobal(data []byte) { | |
| 738 | - | if len(data) < 8 { | |
| 739 | - | return | |
| 740 | - | } | |
| 741 | - | ||
| 742 | - | name := binary.LittleEndian.Uint32(data[0:4]) | |
| 743 | - | ifaceLen := binary.LittleEndian.Uint32(data[4:8]) | |
| 744 | - | ||
| 745 | - | if ifaceLen == 0 || uint64(ifaceLen)+12 > uint64(len(data)) || data[8+ifaceLen-1] != 0 { | |
| 746 | - | return | |
| 647 | + | // EventSignature reports the wl_registry event signatures. | |
| 648 | + | func (r *Registry) EventSignature(opcode uint16) (string, bool) { | |
| 649 | + | switch opcode { | |
| 650 | + | case 0: | |
| 651 | + | return "uint,string,uint,", true | |
| 652 | + | case 1: | |
| 653 | + | return "uint,", true | |
| 747 | 654 | } | |
| 655 | + | return "", false | |
| 656 | + | } | |
| 748 | 657 | ||
| 749 | - | // String includes null terminator in length | |
| 750 | - | iface := string(data[8 : 8+ifaceLen-1]) // -1 to remove null terminator | |
| 751 | - | ||
| 752 | - | // Calculate padding for 32-bit alignment | |
| 753 | - | padding := (4 - (ifaceLen % 4)) % 4 | |
| 754 | - | versionOffset := 8 + int(ifaceLen) + int(padding) | |
| 755 | - | ||
| 756 | - | if len(data) != versionOffset+4 { | |
| 757 | - | return | |
| 658 | + | // Dispatch handles wl_registry.global and wl_registry.global_remove. The | |
| 659 | + | // transport has already validated the body against EventSignature. | |
| 660 | + | func (r *Registry) Dispatch(event *Event) { | |
| 661 | + | switch event.Opcode { | |
| 662 | + | case 0: | |
| 663 | + | r.handleGlobal(event.Uint32(), event.String(), event.Uint32()) | |
| 664 | + | case 1: | |
| 665 | + | r.handleGlobalRemove(event.Uint32()) | |
| 758 | 666 | } | |
| 667 | + | } | |
| 759 | 668 | ||
| 760 | - | version := binary.LittleEndian.Uint32(data[versionOffset:]) | |
| 761 | - | ||
| 762 | - | // Store global | |
| 669 | + | func (r *Registry) handleGlobal(name uint32, iface string, version uint32) { | |
| 763 | 670 | r.mu.Lock() | |
| 764 | - | r.globals[name] = Global{ | |
| 765 | - | Name: name, | |
| 766 | - | Interface: iface, | |
| 767 | - | Version: version, | |
| 768 | - | } | |
| 769 | - | r.mu.Unlock() | |
| 770 | - | ||
| 771 | - | // Call specific handler if registered | |
| 772 | - | r.mu.RLock() | |
| 671 | + | r.globals[name] = Global{Name: name, Interface: iface, Version: version} | |
| 773 | 672 | handlers := append([]GlobalHandler(nil), r.handlers[iface]...) | |
| 774 | 673 | handlers = append(handlers, r.handlers["*"]...) | |
| 775 | - | r.mu.RUnlock() | |
| 674 | + | r.mu.Unlock() | |
| 776 | 675 | for _, handler := range handlers { | |
| 777 | 676 | handler(r, name, version) | |
| 778 | 677 | } | |
| 779 | 678 | } | |
| 780 | 679 | ||
| 781 | - | // handleGlobalRemove handles global removal | |
| 782 | - | func (r *Registry) handleGlobalRemove(data []byte) { | |
| 783 | - | if len(data) < 4 { | |
| 784 | - | return | |
| 785 | - | } | |
| 786 | - | ||
| 787 | - | name := binary.LittleEndian.Uint32(data[0:4]) | |
| 788 | - | ||
| 680 | + | func (r *Registry) handleGlobalRemove(name uint32) { | |
| 789 | 681 | r.mu.Lock() | |
| 790 | 682 | delete(r.globals, name) | |
| 791 | 683 | handlers := append([]RegistryGlobalRemoveHandler(nil), r.removeHandlers...) | |
| @@ -56,21 +56,11 @@ type Display struct { | |||||
| 56 | recvMu sync.Mutex | 56 | recvMu sync.Mutex | ||
| 57 | dispatchMu sync.Mutex // serialize readers without holding receive state across handlers | 57 | dispatchMu sync.Mutex // serialize readers without holding receive state across handlers | ||
| 58 | signatures sync.Map | 58 | signatures sync.Map | ||
| 59 | - | listenerMu sync.Mutex | |||
| 60 | - | listeners sync.Map // map[uint32]map[uint16][]func([]byte) | |||
| 61 | - | ||||
| 62 | - | // High-performance event dispatcher | |||
| 63 | - | dispatcher *EventDispatcher | |||
| 64 | 59 | ||||
| 65 | // Core objects | 60 | // Core objects | ||
| 66 | registry *Registry | 61 | registry *Registry | ||
| 67 | context *Context // Store context once | 62 | context *Context // Store context once | ||
| 68 | 63 | ||||
| 69 | - | // Error state | |||
| 70 | - | lastError error | |||
| 71 | - | lastErrorCode uint32 | |||
| 72 | - | lastErrorObj uint32 | |||
| 73 | - | ||||
| 74 | // Connection-local receive state. rbuf holds bytes read from the socket | 64 | // Connection-local receive state. rbuf holds bytes read from the socket | ||
| 75 | // that do not yet form a complete frame; recvBuf is the scratch buffer | 65 | // that do not yet form a complete frame; recvBuf is the scratch buffer | ||
| 76 | // used for a single read. pendingFDs holds ancillary descriptors that | 66 | // used for a single read. pendingFDs holds ancillary descriptors that | ||
| @@ -86,9 +76,8 @@ type Display struct { | |||||
| 86 | // newDisplay builds a Display over an established connection. | 76 | // newDisplay builds a Display over an established connection. | ||
| 87 | func newDisplay(conn net.Conn) *Display { | 77 | func newDisplay(conn net.Conn) *Display { | ||
| 88 | d := &Display{ | 78 | d := &Display{ | ||
| 89 | - | conn: conn, | 79 | + | conn: conn, |
| 90 | - | nextID: 2, // 1 is reserved for wl_display | 80 | + | nextID: 2, // 1 is reserved for wl_display |
| 91 | - | dispatcher: NewEventDispatcher(), | |||
| 92 | } | 81 | } | ||
| 93 | if uc, ok := conn.(*net.UnixConn); ok { | 82 | if uc, ok := conn.(*net.UnixConn); ok { | ||
| 94 | d.unix = uc | 83 | d.unix = uc | ||
| @@ -138,7 +127,6 @@ func (c *callbackObject) Dispatch(event *Event) { | |||||
| 138 | if event.Opcode != 0 { // done event | 127 | if event.Opcode != 0 { // done event | ||
| 139 | return | 128 | return | ||
| 140 | } | 129 | } | ||
| 141 | - | c.display.notifyListeners(c.id, 0, event.data) | |||
| 142 | c.display.context.Unregister(c) | 130 | c.display.context.Unregister(c) | ||
| 143 | } | 131 | } | ||
| 144 | 132 | ||||
| @@ -212,11 +200,6 @@ func (d *Display) ID() uint32 { | |||||
| 212 | return 1 | 200 | return 1 | ||
| 213 | } | 201 | } | ||
| 214 | 202 | ||||
| 215 | - | // RegisterEventHandler registers a high-performance event handler | |||
| 216 | - | func (d *Display) RegisterEventHandler(objectID uint32, opcode uint16, handler EventHandler) { | |||
| 217 | - | d.dispatcher.RegisterHandler(objectID, opcode, handler) | |||
| 218 | - | } | |||
| 219 | - | ||||
| 220 | // allocateID allocates a new object ID | 203 | // allocateID allocates a new object ID | ||
| 221 | func (d *Display) allocateID() uint32 { | 204 | func (d *Display) allocateID() uint32 { | ||
| 222 | d.idMu.Lock() | 205 | d.idMu.Lock() | ||
| @@ -481,17 +464,13 @@ type signatureProvider interface{ EventSignature(uint16) (string, bool) } | |||||
| 481 | // dispatchFrame delivers one complete frame with precisely its signature's FDs. | 464 | // dispatchFrame delivers one complete frame with precisely its signature's FDs. | ||
| 482 | func (d *Display) dispatchFrame(f receivedFrame) error { | 465 | func (d *Display) dispatchFrame(f receivedFrame) error { | ||
| 483 | d.recvMu.Lock() | 466 | d.recvMu.Lock() | ||
| 484 | - | obj, ev, kind, err := d.prepareFrame(f) | 467 | + | obj, ev, err := d.prepareFrame(f) |
| 485 | d.recvMu.Unlock() | 468 | d.recvMu.Unlock() | ||
| 486 | if err != nil { | 469 | if err != nil { | ||
| 487 | return err | 470 | return err | ||
| 488 | } | 471 | } | ||
| 489 | - | switch kind { | 472 | + | if obj == nil { |
| 490 | - | case 1: | |||
| 491 | return d.handleDisplayEvent(f.opcode, f.body) | 473 | return d.handleDisplayEvent(f.opcode, f.body) | ||
| 492 | - | case 2: | |||
| 493 | - | d.notifyListeners(f.object, f.opcode, f.body) | |||
| 494 | - | return nil | |||
| 495 | } | 474 | } | ||
| 496 | defer func() { | 475 | defer func() { | ||
| 497 | for _, fd := range ev.fds { | 476 | for _, fd := range ev.fds { | ||
| @@ -503,47 +482,31 @@ func (d *Display) dispatchFrame(f receivedFrame) error { | |||||
| 503 | if _, isZombie := obj.(*zombie); isZombie { | 482 | if _, isZombie := obj.(*zombie); isZombie { | ||
| 504 | return nil | 483 | return nil | ||
| 505 | } | 484 | } | ||
| 506 | - | if proxy, ok := obj.(Proxy); ok { | 485 | + | if r, ok := obj.(eventReceiver); ok { |
| 507 | - | proxy.Dispatch(ev) | 486 | + | r.Dispatch(ev) |
| 508 | - | } else { | |||
| 509 | - | d.dispatcher.Dispatch(f.object, f.opcode, f.body) | |||
| 510 | - | d.notifyListeners(f.object, f.opcode, f.body) | |||
| 511 | } | 487 | } | ||
| 512 | return nil | 488 | return nil | ||
| 513 | } | 489 | } | ||
| 514 | 490 | ||||
| 491 | + | // eventReceiver is any registered object that handles its own events: | |||
| 492 | + | // generated proxies, callbacks and the registry. | |||
| 493 | + | type eventReceiver interface{ Dispatch(*Event) } | |||
| 494 | + | ||||
| 515 | // prepareFrame assigns owned descriptors under recvMu, but never calls user code. | 495 | // prepareFrame assigns owned descriptors under recvMu, but never calls user code. | ||
| 516 | - | func (d *Display) prepareFrame(f receivedFrame) (Object, *Event, uint8, error) { | 496 | + | // A nil object with a nil error means the frame targets wl_display. |
| 497 | + | func (d *Display) prepareFrame(f receivedFrame) (Object, *Event, error) { | |||
| 517 | if d.closed.Load() { | 498 | if d.closed.Load() { | ||
| 518 | - | return nil, nil, 0, net.ErrClosed | 499 | + | return nil, nil, net.ErrClosed |
| 519 | } | 500 | } | ||
| 520 | if f.object == displayObjectID { | 501 | if f.object == displayObjectID { | ||
| 521 | if len(d.pendingFDs) != 0 && len(d.rbuf) == 0 { | 502 | if len(d.pendingFDs) != 0 && len(d.rbuf) == 0 { | ||
| 522 | - | return nil, nil, 0, &ProtocolError{Kind: "extra_fd", Err: ErrMalformedFrame} | 503 | + | return nil, nil, &ProtocolError{Kind: "extra_fd", Err: ErrMalformedFrame} |
| 523 | - | } | |||
| 524 | - | return nil, nil, 1, nil | |||
| 525 | - | } | |||
| 526 | - | if f.object == d.registry.id { | |||
| 527 | - | var sig string | |||
| 528 | - | switch f.opcode { | |||
| 529 | - | case 0: | |||
| 530 | - | sig = "uint,string,uint," | |||
| 531 | - | case 1: | |||
| 532 | - | sig = "uint," | |||
| 533 | - | default: | |||
| 534 | - | return nil, nil, 0, &ProtocolError{Kind: "unknown_opcode", Object: f.object, Opcode: f.opcode, Err: ErrUnknownOpcode} | |||
| 535 | } | 504 | } | ||
| 536 | - | if err := validateEventBody(sig, f.body); err != nil { | 505 | + | return nil, nil, nil |
| 537 | - | return nil, nil, 0, &ProtocolError{Kind: "malformed_payload", Object: f.object, Opcode: f.opcode, Err: err} | |||
| 538 | - | } | |||
| 539 | - | if len(d.pendingFDs) != 0 && len(d.rbuf) == 0 { | |||
| 540 | - | return nil, nil, 0, &ProtocolError{Kind: "extra_fd", Object: f.object, Opcode: f.opcode, Err: ErrMalformedFrame} | |||
| 541 | - | } | |||
| 542 | - | return nil, nil, 2, nil | |||
| 543 | } | 506 | } | ||
| 544 | obj, ok := d.objects.Load(f.object) | 507 | obj, ok := d.objects.Load(f.object) | ||
| 545 | if !ok { | 508 | if !ok { | ||
| 546 | - | return nil, nil, 0, &ProtocolError{Kind: "unknown_object", Object: f.object, Opcode: f.opcode, Err: ErrUnknownObject} | 509 | + | return nil, nil, &ProtocolError{Kind: "unknown_object", Object: f.object, Opcode: f.opcode, Err: ErrUnknownObject} |
| 547 | } | 510 | } | ||
| 548 | sigObj := obj | 511 | sigObj := obj | ||
| 549 | z, isZombie := obj.(*zombie) | 512 | z, isZombie := obj.(*zombie) | ||
| @@ -562,14 +525,14 @@ func (d *Display) prepareFrame(f receivedFrame) (Object, *Event, uint8, error) { | |||||
| 562 | } | 525 | } | ||
| 563 | } | 526 | } | ||
| 564 | if !valid { | 527 | if !valid { | ||
| 565 | - | return nil, nil, 0, &ProtocolError{Kind: "unknown_opcode", Object: f.object, Opcode: f.opcode, Err: ErrUnknownOpcode} | 528 | + | return nil, nil, &ProtocolError{Kind: "unknown_opcode", Object: f.object, Opcode: f.opcode, Err: ErrUnknownOpcode} |
| 566 | } | 529 | } | ||
| 567 | if err := validateEventBody(sig, f.body); err != nil { | 530 | if err := validateEventBody(sig, f.body); err != nil { | ||
| 568 | - | return nil, nil, 0, &ProtocolError{Kind: "malformed_payload", Object: f.object, Opcode: f.opcode, Err: err} | 531 | + | return nil, nil, &ProtocolError{Kind: "malformed_payload", Object: f.object, Opcode: f.opcode, Err: err} |
| 569 | } | 532 | } | ||
| 570 | count := strings.Count(sig, "fd,") | 533 | count := strings.Count(sig, "fd,") | ||
| 571 | if len(d.pendingFDs) < count { | 534 | if len(d.pendingFDs) < count { | ||
| 572 | - | return nil, nil, 0, &ProtocolError{Kind: "missing_fd", Object: f.object, Opcode: f.opcode, Err: ErrMalformedFrame} | 535 | + | return nil, nil, &ProtocolError{Kind: "missing_fd", Object: f.object, Opcode: f.opcode, Err: ErrMalformedFrame} |
| 573 | } | 536 | } | ||
| 574 | ev := eventPool.Get().(*Event) | 537 | ev := eventPool.Get().(*Event) | ||
| 575 | *ev = Event{ProxyID: f.object, Opcode: f.opcode, data: f.body, display: d} | 538 | *ev = Event{ProxyID: f.object, Opcode: f.opcode, data: f.body, display: d} | ||
| @@ -588,11 +551,11 @@ func (d *Display) prepareFrame(f receivedFrame) (Object, *Event, uint8, error) { | |||||
| 588 | } | 551 | } | ||
| 589 | *ev = Event{} | 552 | *ev = Event{} | ||
| 590 | eventPool.Put(ev) | 553 | eventPool.Put(ev) | ||
| 591 | - | return nil, nil, 0, &ProtocolError{Kind: "extra_fd", Object: f.object, Opcode: f.opcode, Err: ErrMalformedFrame} | 554 | + | return nil, nil, &ProtocolError{Kind: "extra_fd", Object: f.object, Opcode: f.opcode, Err: ErrMalformedFrame} |
| 592 | } | 555 | } | ||
| 593 | // For a zombie the descriptors were still consumed above so the stream | 556 | // For a zombie the descriptors were still consumed above so the stream | ||
| 594 | // stays aligned; dispatchFrame closes them without running any handler. | 557 | // stays aligned; dispatchFrame closes them without running any handler. | ||
| 595 | - | return obj.(Object), ev, 0, nil | 558 | + | return obj.(Object), ev, nil |
| 596 | } | 559 | } | ||
| 597 | 560 | ||||
| 598 | type signatureKey struct { | 561 | type signatureKey struct { | ||
| @@ -609,27 +572,6 @@ func (d *Display) RegisterEventSignature(object uint32, opcode uint16, signature | |||||
| 609 | d.signatures.Store(signatureKey{object, opcode}, signature) | 572 | d.signatures.Store(signatureKey{object, opcode}, signature) | ||
| 610 | } | 573 | } | ||
| 611 | 574 | ||||
| 612 | - | // notifyListeners runs the listeners registered for an object and opcode. | |||
| 613 | - | func (d *Display) notifyListeners(objectID uint32, opcode uint16, body []byte) { | |||
| 614 | - | listeners, ok := d.listeners.Load(objectID) | |||
| 615 | - | if !ok { | |||
| 616 | - | return | |||
| 617 | - | } | |||
| 618 | - | opcodeMap, ok := listeners.(*sync.Map) | |||
| 619 | - | if !ok { | |||
| 620 | - | return | |||
| 621 | - | } | |||
| 622 | - | handlers, ok := opcodeMap.Load(opcode) | |||
| 623 | - | if !ok { | |||
| 624 | - | return | |||
| 625 | - | } | |||
| 626 | - | for _, handler := range handlers.([]func([]byte)) { | |||
| 627 | - | if handler != nil { | |||
| 628 | - | handler(body) | |||
| 629 | - | } | |||
| 630 | - | } | |||
| 631 | - | } | |||
| 632 | - | ||||
| 633 | // handleDisplayEvent handles events on the display object | 575 | // handleDisplayEvent handles events on the display object | ||
| 634 | func (d *Display) handleDisplayEvent(opcode uint16, data []byte) error { | 576 | func (d *Display) handleDisplayEvent(opcode uint16, data []byte) error { | ||
| 635 | switch opcode { | 577 | switch opcode { | ||
| @@ -640,12 +582,7 @@ func (d *Display) handleDisplayEvent(opcode uint16, data []byte) error { | |||||
| 640 | event := &Event{ProxyID: displayObjectID, Opcode: opcode, data: data, display: d} | 582 | event := &Event{ProxyID: displayObjectID, Opcode: opcode, data: data, display: d} | ||
| 641 | objectID := event.Uint32() | 583 | objectID := event.Uint32() | ||
| 642 | code := event.Uint32() | 584 | code := event.Uint32() | ||
| 643 | - | message := event.String() | 585 | + | return &DisplayError{ObjectID: objectID, Code: code, Message: event.String()} |
| 644 | - | ||||
| 645 | - | d.lastErrorCode = code | |||
| 646 | - | d.lastErrorObj = objectID | |||
| 647 | - | d.lastError = &DisplayError{ObjectID: objectID, Code: code, Message: message} | |||
| 648 | - | return d.lastError | |||
| 649 | 586 | ||||
| 650 | case 1: // delete_id | 587 | case 1: // delete_id | ||
| 651 | if len(data) != 4 { | 588 | if len(data) != 4 { | ||
| @@ -692,35 +629,9 @@ func (d *Display) Roundtrip() error { | |||||
| 692 | } | 629 | } | ||
| 693 | } | 630 | } | ||
| 694 | 631 | ||||
| 695 | - | // AddListener adds an event listener for an object | 632 | + | // getRegistry sends wl_display.get_registry for the bootstrap registry. |
| 696 | - | func (d *Display) AddListener(objectID uint32, opcode uint16, handler func([]byte)) { | |||
| 697 | - | if handler == nil { | |||
| 698 | - | return | |||
| 699 | - | } | |||
| 700 | - | // Load or create listener map for object | |||
| 701 | - | listeners, _ := d.listeners.LoadOrStore(objectID, &sync.Map{}) | |||
| 702 | - | opcodeMap := listeners.(*sync.Map) | |||
| 703 | - | ||||
| 704 | - | d.listenerMu.Lock() | |||
| 705 | - | defer d.listenerMu.Unlock() | |||
| 706 | - | var handlers []func([]byte) | |||
| 707 | - | if old, ok := opcodeMap.Load(opcode); ok { | |||
| 708 | - | handlers = old.([]func([]byte)) | |||
| 709 | - | } | |||
| 710 | - | next := make([]func([]byte), len(handlers)+1) | |||
| 711 | - | copy(next, handlers) | |||
| 712 | - | next[len(handlers)] = handler | |||
| 713 | - | opcodeMap.Store(opcode, next) | |||
| 714 | - | } | |||
| 715 | - | ||||
| 716 | - | // getRegistry gets the global registry | |||
| 717 | func (d *Display) getRegistry() error { | 633 | func (d *Display) getRegistry() error { | ||
| 718 | - | // Add registry listeners | 634 | + | return d.SendRequest(displayObjectID, 1, d.registry.id) |
| 719 | - | d.AddListener(d.registry.id, 0, d.registry.handleGlobal) | |||
| 720 | - | d.AddListener(d.registry.id, 1, d.registry.handleGlobalRemove) | |||
| 721 | - | ||||
| 722 | - | // Send get_registry request (opcode 1) | |||
| 723 | - | return d.SendRequest(1, 1, d.registry.id) | |||
| 724 | } | 635 | } | ||
| 725 | 636 | ||||
| 726 | // Registry returns the global registry | 637 | // Registry returns the global registry | ||
| @@ -733,59 +644,40 @@ func (r *Registry) ID() uint32 { | |||||
| 733 | return r.id | 644 | return r.id | ||
| 734 | } | 645 | } | ||
| 735 | 646 | ||||
| 736 | - | // handleGlobal handles global announcements | 647 | + | // EventSignature reports the wl_registry event signatures. |
| 737 | - | func (r *Registry) handleGlobal(data []byte) { | 648 | + | func (r *Registry) EventSignature(opcode uint16) (string, bool) { |
| 738 | - | if len(data) < 8 { | 649 | + | switch opcode { |
| 739 | - | return | 650 | + | case 0: |
| 740 | - | } | 651 | + | return "uint,string,uint,", true |
| 741 | - | 652 | + | case 1: | |
| 742 | - | name := binary.LittleEndian.Uint32(data[0:4]) | 653 | + | return "uint,", true |
| 743 | - | ifaceLen := binary.LittleEndian.Uint32(data[4:8]) | |||
| 744 | - | ||||
| 745 | - | if ifaceLen == 0 || uint64(ifaceLen)+12 > uint64(len(data)) || data[8+ifaceLen-1] != 0 { | |||
| 746 | - | return | |||
| 747 | } | 654 | } | ||
| 655 | + | return "", false | |||
| 656 | + | } | |||
| 748 | 657 | ||||
| 749 | - | // String includes null terminator in length | 658 | + | // Dispatch handles wl_registry.global and wl_registry.global_remove. The |
| 750 | - | iface := string(data[8 : 8+ifaceLen-1]) // -1 to remove null terminator | 659 | + | // transport has already validated the body against EventSignature. |
| 751 | - | 660 | + | func (r *Registry) Dispatch(event *Event) { | |
| 752 | - | // Calculate padding for 32-bit alignment | 661 | + | switch event.Opcode { |
| 753 | - | padding := (4 - (ifaceLen % 4)) % 4 | 662 | + | case 0: |
| 754 | - | versionOffset := 8 + int(ifaceLen) + int(padding) | 663 | + | r.handleGlobal(event.Uint32(), event.String(), event.Uint32()) |
| 755 | - | 664 | + | case 1: | |
| 756 | - | if len(data) != versionOffset+4 { | 665 | + | r.handleGlobalRemove(event.Uint32()) |
| 757 | - | return | |||
| 758 | } | 666 | } | ||
| 667 | + | } | |||
| 759 | 668 | ||||
| 760 | - | version := binary.LittleEndian.Uint32(data[versionOffset:]) | 669 | + | func (r *Registry) handleGlobal(name uint32, iface string, version uint32) { |
| 761 | - | ||||
| 762 | - | // Store global | |||
| 763 | r.mu.Lock() | 670 | r.mu.Lock() | ||
| 764 | - | r.globals[name] = Global{ | 671 | + | r.globals[name] = Global{Name: name, Interface: iface, Version: version} |
| 765 | - | Name: name, | |||
| 766 | - | Interface: iface, | |||
| 767 | - | Version: version, | |||
| 768 | - | } | |||
| 769 | - | r.mu.Unlock() | |||
| 770 | - | ||||
| 771 | - | // Call specific handler if registered | |||
| 772 | - | r.mu.RLock() | |||
| 773 | handlers := append([]GlobalHandler(nil), r.handlers[iface]...) | 672 | handlers := append([]GlobalHandler(nil), r.handlers[iface]...) | ||
| 774 | handlers = append(handlers, r.handlers["*"]...) | 673 | handlers = append(handlers, r.handlers["*"]...) | ||
| 775 | - | r.mu.RUnlock() | 674 | + | r.mu.Unlock() |
| 776 | for _, handler := range handlers { | 675 | for _, handler := range handlers { | ||
| 777 | handler(r, name, version) | 676 | handler(r, name, version) | ||
| 778 | } | 677 | } | ||
| 779 | } | 678 | } | ||
| 780 | 679 | ||||
| 781 | - | // handleGlobalRemove handles global removal | 680 | + | func (r *Registry) handleGlobalRemove(name uint32) { |
| 782 | - | func (r *Registry) handleGlobalRemove(data []byte) { | |||
| 783 | - | if len(data) < 4 { | |||
| 784 | - | return | |||
| 785 | - | } | |||
| 786 | - | ||||
| 787 | - | name := binary.LittleEndian.Uint32(data[0:4]) | |||
| 788 | - | ||||
| 789 | r.mu.Lock() | 681 | r.mu.Lock() | ||
| 790 | delete(r.globals, name) | 682 | delete(r.globals, name) | ||
| 791 | handlers := append([]RegistryGlobalRemoveHandler(nil), r.removeHandlers...) | 683 | handlers := append([]RegistryGlobalRemoveHandler(nil), r.removeHandlers...) | ||
M client_test.go +0 -98
| @@ -40,86 +40,6 @@ func TestFixed(t *testing.T) { | |||
| 40 | 40 | } | |
| 41 | 41 | } | |
| 42 | 42 | ||
| 43 | - | func TestEventPool(t *testing.T) { | |
| 44 | - | // Test that the global event pool works without requiring a compositor | |
| 45 | - | // Get an event | |
| 46 | - | event := eventPool.Get().(*Event) | |
| 47 | - | if event == nil { | |
| 48 | - | t.Fatal("Expected event from pool, got nil") | |
| 49 | - | } | |
| 50 | - | ||
| 51 | - | // Set some data | |
| 52 | - | event.ProxyID = 123 | |
| 53 | - | event.Opcode = 456 | |
| 54 | - | ||
| 55 | - | // Return to pool | |
| 56 | - | eventPool.Put(event) | |
| 57 | - | ||
| 58 | - | // Get another event - should be reused | |
| 59 | - | event2 := eventPool.Get().(*Event) | |
| 60 | - | if event2 == nil { | |
| 61 | - | t.Fatal("Expected reused event from pool, got nil") | |
| 62 | - | } | |
| 63 | - | ||
| 64 | - | // The pool might return a clean event or the same one, both are valid | |
| 65 | - | t.Logf("Event2: ProxyID=%d, Opcode=%d", event2.ProxyID, event2.Opcode) | |
| 66 | - | ||
| 67 | - | // Clean up | |
| 68 | - | eventPool.Put(event2) | |
| 69 | - | } | |
| 70 | - | ||
| 71 | - | func TestEventDispatcher(t *testing.T) { | |
| 72 | - | dispatcher := NewEventDispatcher() | |
| 73 | - | ||
| 74 | - | called := false | |
| 75 | - | handler := func(event *Event) { | |
| 76 | - | called = true | |
| 77 | - | if event.ProxyID != 123 || event.Opcode != 1 { | |
| 78 | - | t.Errorf("Expected ProxyID=123, Opcode=1, got ProxyID=%d, Opcode=%d", event.ProxyID, event.Opcode) | |
| 79 | - | } | |
| 80 | - | } | |
| 81 | - | ||
| 82 | - | // Register handler | |
| 83 | - | dispatcher.RegisterHandler(123, 1, handler) | |
| 84 | - | ||
| 85 | - | // Dispatch event | |
| 86 | - | dispatcher.Dispatch(123, 1, []byte{}) | |
| 87 | - | ||
| 88 | - | if !called { | |
| 89 | - | t.Error("Handler should have been called") | |
| 90 | - | } | |
| 91 | - | } | |
| 92 | - | ||
| 93 | - | func TestEventDispatcherMultipleHandlers(t *testing.T) { | |
| 94 | - | dispatcher := NewEventDispatcher() | |
| 95 | - | ||
| 96 | - | called1 := false | |
| 97 | - | called2 := false | |
| 98 | - | ||
| 99 | - | handler1 := func(event *Event) { | |
| 100 | - | called1 = true | |
| 101 | - | } | |
| 102 | - | ||
| 103 | - | handler2 := func(event *Event) { | |
| 104 | - | called2 = true | |
| 105 | - | } | |
| 106 | - | ||
| 107 | - | // Register handlers for different opcodes | |
| 108 | - | dispatcher.RegisterHandler(123, 1, handler1) | |
| 109 | - | dispatcher.RegisterHandler(123, 2, handler2) | |
| 110 | - | ||
| 111 | - | // Dispatch different events | |
| 112 | - | dispatcher.Dispatch(123, 1, []byte{}) | |
| 113 | - | dispatcher.Dispatch(123, 2, []byte{}) | |
| 114 | - | ||
| 115 | - | if !called1 { | |
| 116 | - | t.Error("Handler1 should have been called") | |
| 117 | - | } | |
| 118 | - | if !called2 { | |
| 119 | - | t.Error("Handler2 should have been called") | |
| 120 | - | } | |
| 121 | - | } | |
| 122 | - | ||
| 123 | 43 | func TestMessageMarshalingBasic(t *testing.T) { | |
| 124 | 44 | // Create a display to test marshaling (without connecting) | |
| 125 | 45 | d := &Display{ | |
| @@ -339,24 +259,6 @@ func TestRegistryGlobalStorage(t *testing.T) { | |||
| 339 | 259 | } | |
| 340 | 260 | } | |
| 341 | 261 | ||
| 342 | - | func BenchmarkEventDispatch(b *testing.B) { | |
| 343 | - | dispatcher := NewEventDispatcher() | |
| 344 | - | ||
| 345 | - | handler := func(event *Event) { | |
| 346 | - | // Minimal handler for benchmarking | |
| 347 | - | } | |
| 348 | - | ||
| 349 | - | // Register handler | |
| 350 | - | dispatcher.RegisterHandler(123, 1, handler) | |
| 351 | - | ||
| 352 | - | data := []byte{0x01, 0x02, 0x03, 0x04} | |
| 353 | - | ||
| 354 | - | b.ResetTimer() | |
| 355 | - | for i := 0; i < b.N; i++ { | |
| 356 | - | dispatcher.Dispatch(123, 1, data) | |
| 357 | - | } | |
| 358 | - | } | |
| 359 | - | ||
| 360 | 262 | func BenchmarkFixedConversion(b *testing.B) { | |
| 361 | 263 | values := []float64{1.0, 0.5, 123.456, -1.5, 256.789} | |
| 362 | 264 | ||
| @@ -40,86 +40,6 @@ func TestFixed(t *testing.T) { | |||||
| 40 | } | 40 | } | ||
| 41 | } | 41 | } | ||
| 42 | 42 | ||||
| 43 | - | func TestEventPool(t *testing.T) { | |||
| 44 | - | // Test that the global event pool works without requiring a compositor | |||
| 45 | - | // Get an event | |||
| 46 | - | event := eventPool.Get().(*Event) | |||
| 47 | - | if event == nil { | |||
| 48 | - | t.Fatal("Expected event from pool, got nil") | |||
| 49 | - | } | |||
| 50 | - | ||||
| 51 | - | // Set some data | |||
| 52 | - | event.ProxyID = 123 | |||
| 53 | - | event.Opcode = 456 | |||
| 54 | - | ||||
| 55 | - | // Return to pool | |||
| 56 | - | eventPool.Put(event) | |||
| 57 | - | ||||
| 58 | - | // Get another event - should be reused | |||
| 59 | - | event2 := eventPool.Get().(*Event) | |||
| 60 | - | if event2 == nil { | |||
| 61 | - | t.Fatal("Expected reused event from pool, got nil") | |||
| 62 | - | } | |||
| 63 | - | ||||
| 64 | - | // The pool might return a clean event or the same one, both are valid | |||
| 65 | - | t.Logf("Event2: ProxyID=%d, Opcode=%d", event2.ProxyID, event2.Opcode) | |||
| 66 | - | ||||
| 67 | - | // Clean up | |||
| 68 | - | eventPool.Put(event2) | |||
| 69 | - | } | |||
| 70 | - | ||||
| 71 | - | func TestEventDispatcher(t *testing.T) { | |||
| 72 | - | dispatcher := NewEventDispatcher() | |||
| 73 | - | ||||
| 74 | - | called := false | |||
| 75 | - | handler := func(event *Event) { | |||
| 76 | - | called = true | |||
| 77 | - | if event.ProxyID != 123 || event.Opcode != 1 { | |||
| 78 | - | t.Errorf("Expected ProxyID=123, Opcode=1, got ProxyID=%d, Opcode=%d", event.ProxyID, event.Opcode) | |||
| 79 | - | } | |||
| 80 | - | } | |||
| 81 | - | ||||
| 82 | - | // Register handler | |||
| 83 | - | dispatcher.RegisterHandler(123, 1, handler) | |||
| 84 | - | ||||
| 85 | - | // Dispatch event | |||
| 86 | - | dispatcher.Dispatch(123, 1, []byte{}) | |||
| 87 | - | ||||
| 88 | - | if !called { | |||
| 89 | - | t.Error("Handler should have been called") | |||
| 90 | - | } | |||
| 91 | - | } | |||
| 92 | - | ||||
| 93 | - | func TestEventDispatcherMultipleHandlers(t *testing.T) { | |||
| 94 | - | dispatcher := NewEventDispatcher() | |||
| 95 | - | ||||
| 96 | - | called1 := false | |||
| 97 | - | called2 := false | |||
| 98 | - | ||||
| 99 | - | handler1 := func(event *Event) { | |||
| 100 | - | called1 = true | |||
| 101 | - | } | |||
| 102 | - | ||||
| 103 | - | handler2 := func(event *Event) { | |||
| 104 | - | called2 = true | |||
| 105 | - | } | |||
| 106 | - | ||||
| 107 | - | // Register handlers for different opcodes | |||
| 108 | - | dispatcher.RegisterHandler(123, 1, handler1) | |||
| 109 | - | dispatcher.RegisterHandler(123, 2, handler2) | |||
| 110 | - | ||||
| 111 | - | // Dispatch different events | |||
| 112 | - | dispatcher.Dispatch(123, 1, []byte{}) | |||
| 113 | - | dispatcher.Dispatch(123, 2, []byte{}) | |||
| 114 | - | ||||
| 115 | - | if !called1 { | |||
| 116 | - | t.Error("Handler1 should have been called") | |||
| 117 | - | } | |||
| 118 | - | if !called2 { | |||
| 119 | - | t.Error("Handler2 should have been called") | |||
| 120 | - | } | |||
| 121 | - | } | |||
| 122 | - | ||||
| 123 | func TestMessageMarshalingBasic(t *testing.T) { | 43 | func TestMessageMarshalingBasic(t *testing.T) { | ||
| 124 | // Create a display to test marshaling (without connecting) | 44 | // Create a display to test marshaling (without connecting) | ||
| 125 | d := &Display{ | 45 | d := &Display{ | ||
| @@ -339,24 +259,6 @@ func TestRegistryGlobalStorage(t *testing.T) { | |||||
| 339 | } | 259 | } | ||
| 340 | } | 260 | } | ||
| 341 | 261 | ||||
| 342 | - | func BenchmarkEventDispatch(b *testing.B) { | |||
| 343 | - | dispatcher := NewEventDispatcher() | |||
| 344 | - | ||||
| 345 | - | handler := func(event *Event) { | |||
| 346 | - | // Minimal handler for benchmarking | |||
| 347 | - | } | |||
| 348 | - | ||||
| 349 | - | // Register handler | |||
| 350 | - | dispatcher.RegisterHandler(123, 1, handler) | |||
| 351 | - | ||||
| 352 | - | data := []byte{0x01, 0x02, 0x03, 0x04} | |||
| 353 | - | ||||
| 354 | - | b.ResetTimer() | |||
| 355 | - | for i := 0; i < b.N; i++ { | |||
| 356 | - | dispatcher.Dispatch(123, 1, data) | |||
| 357 | - | } | |||
| 358 | - | } | |||
| 359 | - | ||||
| 360 | func BenchmarkFixedConversion(b *testing.B) { | 262 | func BenchmarkFixedConversion(b *testing.B) { | ||
| 361 | values := []float64{1.0, 0.5, 123.456, -1.5, 256.789} | 263 | values := []float64{1.0, 0.5, 123.456, -1.5, 256.789} | ||
| 362 | 264 | ||||
M core.go +4 -0
| @@ -30,6 +30,10 @@ type BaseProxy struct { | |||
| 30 | 30 | context *Context | |
| 31 | 31 | } | |
| 32 | 32 | ||
| 33 | + | // eventPool recycles Events across dispatches; an Event is only valid for | |
| 34 | + | // the duration of the handler call. | |
| 35 | + | var eventPool = sync.Pool{New: func() any { return &Event{} }} | |
| 36 | + | ||
| 33 | 37 | // Event represents a Wayland protocol event | |
| 34 | 38 | type Event struct { | |
| 35 | 39 | ProxyID uint32 | |
| @@ -30,6 +30,10 @@ type BaseProxy struct { | |||||
| 30 | context *Context | 30 | context *Context | ||
| 31 | } | 31 | } | ||
| 32 | 32 | ||||
| 33 | + | // eventPool recycles Events across dispatches; an Event is only valid for | |||
| 34 | + | // the duration of the handler call. | |||
| 35 | + | var eventPool = sync.Pool{New: func() any { return &Event{} }} | |||
| 36 | + | ||||
| 33 | // Event represents a Wayland protocol event | 37 | // Event represents a Wayland protocol event | ||
| 34 | type Event struct { | 38 | type Event struct { | ||
| 35 | ProxyID uint32 | 39 | ProxyID uint32 | ||
D event_pool.go +0 -185
| @@ -1,185 +0,0 @@ | |||
| 1 | - | //go:build linux | |
| 2 | - | // +build linux | |
| 3 | - | ||
| 4 | - | package wlturbo | |
| 5 | - | ||
| 6 | - | import ( | |
| 7 | - | "sync" | |
| 8 | - | "sync/atomic" | |
| 9 | - | ) | |
| 10 | - | ||
| 11 | - | // Event pool for zero-allocation event handling | |
| 12 | - | var eventPool = sync.Pool{ | |
| 13 | - | New: func() interface{} { | |
| 14 | - | return &Event{ | |
| 15 | - | data: make([]byte, 0, 4096), // Pre-allocate 4KB | |
| 16 | - | } | |
| 17 | - | }, | |
| 18 | - | } | |
| 19 | - | ||
| 20 | - | // EventHandler is a function type for handling events with zero allocations | |
| 21 | - | type EventHandler func(event *Event) | |
| 22 | - | ||
| 23 | - | // EventDispatcher handles high-performance event dispatching | |
| 24 | - | type EventDispatcher struct { | |
| 25 | - | // Lock-free handler lookup using atomic operations | |
| 26 | - | handlers [1024]atomic.Pointer[handlerEntry] // Fixed-size array for object IDs 0-1023 | |
| 27 | - | ||
| 28 | - | // Extended handlers for object IDs >= 1024 (rare case) | |
| 29 | - | extHandlers sync.Map | |
| 30 | - | } | |
| 31 | - | ||
| 32 | - | // handlerEntry stores handlers for a specific object | |
| 33 | - | type handlerEntry struct { | |
| 34 | - | // Array of handlers indexed by opcode (most objects have < 16 opcodes) | |
| 35 | - | handlers [32]EventHandler | |
| 36 | - | ||
| 37 | - | // Extended handlers for opcodes >= 32 (very rare) | |
| 38 | - | extHandlers *sync.Map | |
| 39 | - | } | |
| 40 | - | ||
| 41 | - | // NewEventDispatcher creates a high-performance event dispatcher | |
| 42 | - | func NewEventDispatcher() *EventDispatcher { | |
| 43 | - | return &EventDispatcher{} | |
| 44 | - | } | |
| 45 | - | ||
| 46 | - | // RegisterHandler registers an event handler with zero allocation in common case | |
| 47 | - | func (d *EventDispatcher) RegisterHandler(objectID uint32, opcode uint16, handler EventHandler) { | |
| 48 | - | if objectID < 1024 { | |
| 49 | - | // Fast path: use lock-free array | |
| 50 | - | for { | |
| 51 | - | entry := d.handlers[objectID].Load() | |
| 52 | - | if entry == nil { | |
| 53 | - | // Create new entry | |
| 54 | - | newEntry := &handlerEntry{} | |
| 55 | - | if opcode < 32 { | |
| 56 | - | newEntry.handlers[opcode] = handler | |
| 57 | - | } else { | |
| 58 | - | newEntry.extHandlers = &sync.Map{} | |
| 59 | - | newEntry.extHandlers.Store(opcode, handler) | |
| 60 | - | } | |
| 61 | - | ||
| 62 | - | // Try to set atomically | |
| 63 | - | if d.handlers[objectID].CompareAndSwap(nil, newEntry) { | |
| 64 | - | return | |
| 65 | - | } | |
| 66 | - | // Retry if someone else created it | |
| 67 | - | continue | |
| 68 | - | } | |
| 69 | - | ||
| 70 | - | // Entry exists, update it | |
| 71 | - | if opcode < 32 { | |
| 72 | - | // Direct array access (no locking needed for write) | |
| 73 | - | entry.handlers[opcode] = handler | |
| 74 | - | } else { | |
| 75 | - | // Extended handlers | |
| 76 | - | if entry.extHandlers == nil { | |
| 77 | - | entry.extHandlers = &sync.Map{} | |
| 78 | - | } | |
| 79 | - | entry.extHandlers.Store(opcode, handler) | |
| 80 | - | } | |
| 81 | - | return | |
| 82 | - | } | |
| 83 | - | } else { | |
| 84 | - | // Slow path: use sync.Map for large object IDs | |
| 85 | - | entry, _ := d.extHandlers.LoadOrStore(objectID, &handlerEntry{}) | |
| 86 | - | h := entry.(*handlerEntry) | |
| 87 | - | ||
| 88 | - | if opcode < 32 { | |
| 89 | - | h.handlers[opcode] = handler | |
| 90 | - | } else { | |
| 91 | - | if h.extHandlers == nil { | |
| 92 | - | h.extHandlers = &sync.Map{} | |
| 93 | - | } | |
| 94 | - | h.extHandlers.Store(opcode, handler) | |
| 95 | - | } | |
| 96 | - | } | |
| 97 | - | } | |
| 98 | - | ||
| 99 | - | // Dispatch dispatches an event with minimal overhead | |
| 100 | - | // | |
| 101 | - | //go:inline | |
| 102 | - | func (d *EventDispatcher) Dispatch(objectID uint32, opcode uint16, data []byte) { | |
| 103 | - | var handler EventHandler | |
| 104 | - | ||
| 105 | - | // Fast lookup path | |
| 106 | - | if objectID < 1024 { | |
| 107 | - | entry := d.handlers[objectID].Load() | |
| 108 | - | if entry != nil { | |
| 109 | - | if opcode < 32 { | |
| 110 | - | handler = entry.handlers[opcode] | |
| 111 | - | } else if entry.extHandlers != nil { | |
| 112 | - | if h, ok := entry.extHandlers.Load(opcode); ok { | |
| 113 | - | handler = h.(EventHandler) | |
| 114 | - | } | |
| 115 | - | } | |
| 116 | - | } | |
| 117 | - | } else { | |
| 118 | - | // Slow path for large object IDs | |
| 119 | - | if entry, ok := d.extHandlers.Load(objectID); ok { | |
| 120 | - | h := entry.(*handlerEntry) | |
| 121 | - | if opcode < 32 { | |
| 122 | - | handler = h.handlers[opcode] | |
| 123 | - | } else if h.extHandlers != nil { | |
| 124 | - | if fn, ok := h.extHandlers.Load(opcode); ok { | |
| 125 | - | handler = fn.(EventHandler) | |
| 126 | - | } | |
| 127 | - | } | |
| 128 | - | } | |
| 129 | - | } | |
| 130 | - | ||
| 131 | - | if handler == nil { | |
| 132 | - | return | |
| 133 | - | } | |
| 134 | - | ||
| 135 | - | // Get event from pool | |
| 136 | - | event := eventPool.Get().(*Event) | |
| 137 | - | event.ProxyID = objectID | |
| 138 | - | event.Opcode = opcode | |
| 139 | - | event.data = append(event.data[:0], data...) // Reuse backing array | |
| 140 | - | event.offset = 0 | |
| 141 | - | ||
| 142 | - | // Call handler | |
| 143 | - | handler(event) | |
| 144 | - | ||
| 145 | - | // Return to pool | |
| 146 | - | eventPool.Put(event) | |
| 147 | - | } | |
| 148 | - | ||
| 149 | - | // BatchDispatch processes multiple events in a batch for better cache locality | |
| 150 | - | func (d *EventDispatcher) BatchDispatch(events []RawEvent) { | |
| 151 | - | // Process events in batches to improve cache performance | |
| 152 | - | for i := range events { | |
| 153 | - | e := &events[i] | |
| 154 | - | d.Dispatch(e.ObjectID, e.Opcode, e.Data) | |
| 155 | - | } | |
| 156 | - | } | |
| 157 | - | ||
| 158 | - | // RawEvent represents a raw event before dispatch | |
| 159 | - | type RawEvent struct { | |
| 160 | - | ObjectID uint32 | |
| 161 | - | Opcode uint16 | |
| 162 | - | Data []byte | |
| 163 | - | } | |
| 164 | - | ||
| 165 | - | // Ensure cache line alignment for hot data structures | |
| 166 | - | type cacheLinePad [64]byte | |
| 167 | - | ||
| 168 | - | // PerformanceStats tracks dispatcher performance | |
| 169 | - | type PerformanceStats struct { | |
| 170 | - | EventsDispatched atomic.Uint64 | |
| 171 | - | _ cacheLinePad | |
| 172 | - | NanosecondsSpent atomic.Uint64 | |
| 173 | - | _ cacheLinePad | |
| 174 | - | CacheMisses atomic.Uint64 | |
| 175 | - | _ cacheLinePad | |
| 176 | - | } | |
| 177 | - | ||
| 178 | - | // GetAverageLatency returns average dispatch latency in nanoseconds | |
| 179 | - | func (s *PerformanceStats) GetAverageLatency() uint64 { | |
| 180 | - | events := s.EventsDispatched.Load() | |
| 181 | - | if events == 0 { | |
| 182 | - | return 0 | |
| 183 | - | } | |
| 184 | - | return s.NanosecondsSpent.Load() / events | |
| 185 | - | } | |
| @@ -1,185 +0,0 @@ | |||||
| 1 | - | //go:build linux | |||
| 2 | - | // +build linux | |||
| 3 | - | ||||
| 4 | - | package wlturbo | |||
| 5 | - | ||||
| 6 | - | import ( | |||
| 7 | - | "sync" | |||
| 8 | - | "sync/atomic" | |||
| 9 | - | ) | |||
| 10 | - | ||||
| 11 | - | // Event pool for zero-allocation event handling | |||
| 12 | - | var eventPool = sync.Pool{ | |||
| 13 | - | New: func() interface{} { | |||
| 14 | - | return &Event{ | |||
| 15 | - | data: make([]byte, 0, 4096), // Pre-allocate 4KB | |||
| 16 | - | } | |||
| 17 | - | }, | |||
| 18 | - | } | |||
| 19 | - | ||||
| 20 | - | // EventHandler is a function type for handling events with zero allocations | |||
| 21 | - | type EventHandler func(event *Event) | |||
| 22 | - | ||||
| 23 | - | // EventDispatcher handles high-performance event dispatching | |||
| 24 | - | type EventDispatcher struct { | |||
| 25 | - | // Lock-free handler lookup using atomic operations | |||
| 26 | - | handlers [1024]atomic.Pointer[handlerEntry] // Fixed-size array for object IDs 0-1023 | |||
| 27 | - | ||||
| 28 | - | // Extended handlers for object IDs >= 1024 (rare case) | |||
| 29 | - | extHandlers sync.Map | |||
| 30 | - | } | |||
| 31 | - | ||||
| 32 | - | // handlerEntry stores handlers for a specific object | |||
| 33 | - | type handlerEntry struct { | |||
| 34 | - | // Array of handlers indexed by opcode (most objects have < 16 opcodes) | |||
| 35 | - | handlers [32]EventHandler | |||
| 36 | - | ||||
| 37 | - | // Extended handlers for opcodes >= 32 (very rare) | |||
| 38 | - | extHandlers *sync.Map | |||
| 39 | - | } | |||
| 40 | - | ||||
| 41 | - | // NewEventDispatcher creates a high-performance event dispatcher | |||
| 42 | - | func NewEventDispatcher() *EventDispatcher { | |||
| 43 | - | return &EventDispatcher{} | |||
| 44 | - | } | |||
| 45 | - | ||||
| 46 | - | // RegisterHandler registers an event handler with zero allocation in common case | |||
| 47 | - | func (d *EventDispatcher) RegisterHandler(objectID uint32, opcode uint16, handler EventHandler) { | |||
| 48 | - | if objectID < 1024 { | |||
| 49 | - | // Fast path: use lock-free array | |||
| 50 | - | for { | |||
| 51 | - | entry := d.handlers[objectID].Load() | |||
| 52 | - | if entry == nil { | |||
| 53 | - | // Create new entry | |||
| 54 | - | newEntry := &handlerEntry{} | |||
| 55 | - | if opcode < 32 { | |||
| 56 | - | newEntry.handlers[opcode] = handler | |||
| 57 | - | } else { | |||
| 58 | - | newEntry.extHandlers = &sync.Map{} | |||
| 59 | - | newEntry.extHandlers.Store(opcode, handler) | |||
| 60 | - | } | |||
| 61 | - | ||||
| 62 | - | // Try to set atomically | |||
| 63 | - | if d.handlers[objectID].CompareAndSwap(nil, newEntry) { | |||
| 64 | - | return | |||
| 65 | - | } | |||
| 66 | - | // Retry if someone else created it | |||
| 67 | - | continue | |||
| 68 | - | } | |||
| 69 | - | ||||
| 70 | - | // Entry exists, update it | |||
| 71 | - | if opcode < 32 { | |||
| 72 | - | // Direct array access (no locking needed for write) | |||
| 73 | - | entry.handlers[opcode] = handler | |||
| 74 | - | } else { | |||
| 75 | - | // Extended handlers | |||
| 76 | - | if entry.extHandlers == nil { | |||
| 77 | - | entry.extHandlers = &sync.Map{} | |||
| 78 | - | } | |||
| 79 | - | entry.extHandlers.Store(opcode, handler) | |||
| 80 | - | } | |||
| 81 | - | return | |||
| 82 | - | } | |||
| 83 | - | } else { | |||
| 84 | - | // Slow path: use sync.Map for large object IDs | |||
| 85 | - | entry, _ := d.extHandlers.LoadOrStore(objectID, &handlerEntry{}) | |||
| 86 | - | h := entry.(*handlerEntry) | |||
| 87 | - | ||||
| 88 | - | if opcode < 32 { | |||
| 89 | - | h.handlers[opcode] = handler | |||
| 90 | - | } else { | |||
| 91 | - | if h.extHandlers == nil { | |||
| 92 | - | h.extHandlers = &sync.Map{} | |||
| 93 | - | } | |||
| 94 | - | h.extHandlers.Store(opcode, handler) | |||
| 95 | - | } | |||
| 96 | - | } | |||
| 97 | - | } | |||
| 98 | - | ||||
| 99 | - | // Dispatch dispatches an event with minimal overhead | |||
| 100 | - | // | |||
| 101 | - | //go:inline | |||
| 102 | - | func (d *EventDispatcher) Dispatch(objectID uint32, opcode uint16, data []byte) { | |||
| 103 | - | var handler EventHandler | |||
| 104 | - | ||||
| 105 | - | // Fast lookup path | |||
| 106 | - | if objectID < 1024 { | |||
| 107 | - | entry := d.handlers[objectID].Load() | |||
| 108 | - | if entry != nil { | |||
| 109 | - | if opcode < 32 { | |||
| 110 | - | handler = entry.handlers[opcode] | |||
| 111 | - | } else if entry.extHandlers != nil { | |||
| 112 | - | if h, ok := entry.extHandlers.Load(opcode); ok { | |||
| 113 | - | handler = h.(EventHandler) | |||
| 114 | - | } | |||
| 115 | - | } | |||
| 116 | - | } | |||
| 117 | - | } else { | |||
| 118 | - | // Slow path for large object IDs | |||
| 119 | - | if entry, ok := d.extHandlers.Load(objectID); ok { | |||
| 120 | - | h := entry.(*handlerEntry) | |||
| 121 | - | if opcode < 32 { | |||
| 122 | - | handler = h.handlers[opcode] | |||
| 123 | - | } else if h.extHandlers != nil { | |||
| 124 | - | if fn, ok := h.extHandlers.Load(opcode); ok { | |||
| 125 | - | handler = fn.(EventHandler) | |||
| 126 | - | } | |||
| 127 | - | } | |||
| 128 | - | } | |||
| 129 | - | } | |||
| 130 | - | ||||
| 131 | - | if handler == nil { | |||
| 132 | - | return | |||
| 133 | - | } | |||
| 134 | - | ||||
| 135 | - | // Get event from pool | |||
| 136 | - | event := eventPool.Get().(*Event) | |||
| 137 | - | event.ProxyID = objectID | |||
| 138 | - | event.Opcode = opcode | |||
| 139 | - | event.data = append(event.data[:0], data...) // Reuse backing array | |||
| 140 | - | event.offset = 0 | |||
| 141 | - | ||||
| 142 | - | // Call handler | |||
| 143 | - | handler(event) | |||
| 144 | - | ||||
| 145 | - | // Return to pool | |||
| 146 | - | eventPool.Put(event) | |||
| 147 | - | } | |||
| 148 | - | ||||
| 149 | - | // BatchDispatch processes multiple events in a batch for better cache locality | |||
| 150 | - | func (d *EventDispatcher) BatchDispatch(events []RawEvent) { | |||
| 151 | - | // Process events in batches to improve cache performance | |||
| 152 | - | for i := range events { | |||
| 153 | - | e := &events[i] | |||
| 154 | - | d.Dispatch(e.ObjectID, e.Opcode, e.Data) | |||
| 155 | - | } | |||
| 156 | - | } | |||
| 157 | - | ||||
| 158 | - | // RawEvent represents a raw event before dispatch | |||
| 159 | - | type RawEvent struct { | |||
| 160 | - | ObjectID uint32 | |||
| 161 | - | Opcode uint16 | |||
| 162 | - | Data []byte | |||
| 163 | - | } | |||
| 164 | - | ||||
| 165 | - | // Ensure cache line alignment for hot data structures | |||
| 166 | - | type cacheLinePad [64]byte | |||
| 167 | - | ||||
| 168 | - | // PerformanceStats tracks dispatcher performance | |||
| 169 | - | type PerformanceStats struct { | |||
| 170 | - | EventsDispatched atomic.Uint64 | |||
| 171 | - | _ cacheLinePad | |||
| 172 | - | NanosecondsSpent atomic.Uint64 | |||
| 173 | - | _ cacheLinePad | |||
| 174 | - | CacheMisses atomic.Uint64 | |||
| 175 | - | _ cacheLinePad | |||
| 176 | - | } | |||
| 177 | - | ||||
| 178 | - | // GetAverageLatency returns average dispatch latency in nanoseconds | |||
| 179 | - | func (s *PerformanceStats) GetAverageLatency() uint64 { | |||
| 180 | - | events := s.EventsDispatched.Load() | |||
| 181 | - | if events == 0 { | |||
| 182 | - | return 0 | |||
| 183 | - | } | |||
| 184 | - | return s.NanosecondsSpent.Load() / events | |||
| 185 | - | } | |||
M hardening_test.go +1 -1
| @@ -51,7 +51,7 @@ func TestRegistryGlobalHandlersAccumulate(t *testing.T) { | |||
| 51 | 51 | d.registry.AddGlobalHandler(countingGlobalHandler{&a}) | |
| 52 | 52 | d.registry.AddGlobalHandler(countingGlobalHandler{&b}) | |
| 53 | 53 | d.registry.AddHandler("abc", func(*Registry, uint32, uint32) { specific++ }) | |
| 54 | - | d.registry.handleGlobal([]byte{1, 0, 0, 0, 4, 0, 0, 0, 'a', 'b', 'c', 0, 1, 0, 0, 0}) | |
| 54 | + | d.registry.handleGlobal(1, "abc", 1) | |
| 55 | 55 | if a != 1 || b != 1 || specific != 1 { | |
| 56 | 56 | t.Fatalf("calls a=%d b=%d specific=%d, want 1 each", a, b, specific) | |
| 57 | 57 | } | |
| @@ -51,7 +51,7 @@ func TestRegistryGlobalHandlersAccumulate(t *testing.T) { | |||||
| 51 | d.registry.AddGlobalHandler(countingGlobalHandler{&a}) | 51 | d.registry.AddGlobalHandler(countingGlobalHandler{&a}) | ||
| 52 | d.registry.AddGlobalHandler(countingGlobalHandler{&b}) | 52 | d.registry.AddGlobalHandler(countingGlobalHandler{&b}) | ||
| 53 | d.registry.AddHandler("abc", func(*Registry, uint32, uint32) { specific++ }) | 53 | d.registry.AddHandler("abc", func(*Registry, uint32, uint32) { specific++ }) | ||
| 54 | - | d.registry.handleGlobal([]byte{1, 0, 0, 0, 4, 0, 0, 0, 'a', 'b', 'c', 0, 1, 0, 0, 0}) | 54 | + | d.registry.handleGlobal(1, "abc", 1) |
| 55 | if a != 1 || b != 1 || specific != 1 { | 55 | if a != 1 || b != 1 || specific != 1 { | ||
| 56 | t.Fatalf("calls a=%d b=%d specific=%d, want 1 each", a, b, specific) | 56 | t.Fatalf("calls a=%d b=%d specific=%d, want 1 each", a, b, specific) | ||
| 57 | } | 57 | } | ||