| // Copyright (C) 2016 The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| #include "RenderChannelImpl.h" |
| |
| #include <algorithm> |
| #include <utility> |
| |
| #include <assert.h> |
| #include <string.h> |
| |
| namespace emugl { |
| |
| RenderChannelImpl::RenderChannelImpl(std::shared_ptr<RendererImpl> renderer) |
| : mRenderer(std::move(renderer)), mState(State::Empty) { |
| assert(mState.is_lock_free()); // rethink it if this fails - we've already |
| // got a lock, use it instead. |
| } |
| |
| void RenderChannelImpl::setEventCallback( |
| RenderChannelImpl::EventCallback callback) { |
| mOnEvent = std::move(callback); |
| |
| // Reset the current state to make sure the new subscriber gets it. |
| mState.store(State::Empty, std::memory_order_release); |
| onEvent(true); |
| } |
| |
| bool RenderChannelImpl::write(ChannelBuffer&& buffer) { |
| const bool res = mFromGuest.send(std::move(buffer)); |
| onEvent(true); |
| return res; |
| } |
| |
| bool RenderChannelImpl::read(ChannelBuffer* buffer, CallType type) { |
| if (type == CallType::Nonblocking && mToGuest.size() == 0) { |
| return false; |
| } |
| const bool res = mToGuest.receive(buffer); |
| onEvent(true); |
| return res; |
| } |
| |
| void RenderChannelImpl::stop() { |
| stop(true); |
| } |
| |
| void RenderChannelImpl::forceStop() { |
| stop(false); |
| } |
| |
| void RenderChannelImpl::stop(bool byGuest) { |
| if (mStopped) { |
| return; |
| } |
| mStopped = true; |
| mFromGuest.stop(); |
| mToGuest.stop(); |
| onEvent(byGuest); |
| } |
| |
| bool RenderChannelImpl::isStopped() const { |
| return mStopped; |
| } |
| |
| void RenderChannelImpl::writeToGuest(ChannelBuffer&& buf) { |
| mToGuest.send(std::move(buf)); |
| onEvent(false); |
| } |
| |
| size_t RenderChannelImpl::readFromGuest(ChannelBuffer::value_type* buf, |
| size_t size, |
| bool blocking) { |
| assert(buf); |
| |
| size_t read = 0; |
| const auto bufEnd = buf + size; |
| while (buf != bufEnd && !mStopped) { |
| if (mFromGuestBufferLeft == 0) { |
| if (mFromGuest.size() == 0 && (read > 0 || !blocking)) { |
| break; |
| } |
| if (!mFromGuest.receive(&mFromGuestBuffer)) { |
| break; |
| } |
| mFromGuestBufferLeft = mFromGuestBuffer.size(); |
| } |
| |
| const size_t curSize = |
| std::min<size_t>(bufEnd - buf, mFromGuestBufferLeft); |
| memcpy(buf, mFromGuestBuffer.data() + |
| (mFromGuestBuffer.size() - mFromGuestBufferLeft), |
| curSize); |
| |
| read += curSize; |
| buf += curSize; |
| mFromGuestBufferLeft -= curSize; |
| } |
| onEvent(false); |
| return read; |
| } |
| |
| void RenderChannelImpl::onEvent(bool byGuest) { |
| if (!mOnEvent) { |
| return; |
| } |
| |
| // We need to make sure that the state returned from calcState() remains |
| // valid when we write it into mState; this means we need to lock both |
| // calcState() and mState assignment with the same lock - otherwise it is |
| // possible (and it happened before the lock was added) that some other |
| // thread would overwrite a newer state with its older value, e.g.: |
| // - thread 1 reads the last message from |mToGuest| |
| // - thread 1 enters onEvent() |
| // - thread 1 calculates state 1 = "can't read", switches out |
| // - thread 2 writes some data into |mToGuest| |
| // - thread 2 enters onEvent() |
| // - thread 2 calculates state 2 = "can read" |
| // - thread 2 gets the lock |
| // - thread 2 updates |mState| to "can read", unlocks, switches out |
| // - thread 1 gets the lock |
| // - thread 1 overwrites |mState| with state 1 = "can't read" |
| // - thread 1 unlocks and calls the |mOnEvent| callback. |
| // |
| // The result is that state 2 = "can read" is completely lost - callback |
| // is never called when |mState| is "can read". |
| // |
| // But if the whole block of code is locked, threads can't overwrite newer |
| // |mState| with some older value, and the described situation would never |
| // happen. |
| android::base::AutoLock lock(mStateLock); |
| const State newState = calcState(); |
| if (mState != newState) { |
| mState = newState; |
| lock.unlock(); |
| |
| mOnEvent(newState, |
| byGuest ? EventSource::Client : EventSource::RenderChannel); |
| } |
| } |
| |
| RenderChannel::State RenderChannelImpl::calcState() const { |
| State state = State::Empty; |
| if (mStopped) { |
| state = State::Stopped; |
| } else { |
| if (mFromGuest.size() < mFromGuest.capacity()) { |
| state |= State::CanWrite; |
| } |
| if (mToGuest.size() > 0) { |
| state |= State::CanRead; |
| } |
| } |
| return state; |
| } |
| |
| } // namespace emugl |